/* 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 ); } }