forked from a1batross/Paranoia2_original
1248 lines
27 KiB
C++
1248 lines
27 KiB
C++
/***
|
||
*
|
||
* 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 <string.h>
|
||
#include <stdio.h>
|
||
|
||
#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;
|
||
}
|