• (Source Mod) Is it possible to embed a material that will be created when a weapon is fired?
    4 replies, posted
Is it possible to override the material stated $surfaceprop command in a weapon, and if so how do I go about doing it? It is basically a copy and paste of SMG1 code which has been modified to turn it into spraypaint and whenever bullets hit the surface it needs to override the $surfaceprop stated material so that it paints a custom decal. Here is the coding for the weapon: [CODE]//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "basehlcombatweapon.h" #include "NPCevent.h" #include "basecombatcharacter.h" #include "AI_BaseNPC.h" #include "player.h" #include "game.h" #include "in_buttons.h" #include "AI_Memory.h" #include "soundent.h" #include "rumble_shared.h" #include "gamestats.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" class CWeaponSPRAYPAINT : public CHLSelectFireMachineGun { DECLARE_DATADESC(); public: DECLARE_CLASS( CWeaponSpraypaint, CHLSelectFireMachineGun ); CWeaponSPRAYPAINT(); DECLARE_SERVERCLASS(); void Precache( void ); void AddViewKick( void ); void SecondaryAttack( void ); int GetMinBurst() { return 2; } int GetMaxBurst() { return 5; } virtual void Equip( CBaseCombatCharacter *pOwner ); bool Reload( void ); float GetFireRate( void ) { return 0.075f; } // 13.3hz int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } int WeaponRangeAttack2Condition( float flDot, float flDist ); Activity GetPrimaryAttackActivity( void ); virtual const Vector& GetBulletSpread( void ) { static const Vector cone = VECTOR_CONE_5DEGREES; return cone; } const WeaponProficiencyInfo_t *GetProficiencyValues(); void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); DECLARE_ACTTABLE(); protected: Vector m_vecTossVelocity; float m_flNextGrenadeCheck; }; IMPLEMENT_SERVERCLASS_ST(CWeaponSPRAYPAINT, DT_WeaponSMG1) END_SEND_TABLE() LINK_ENTITY_TO_CLASS( weapon_spraypaint, CWeaponSPRAYPAINT ); PRECACHE_WEAPON_REGISTER(weapon_spraypaint); BEGIN_DATADESC( CWeaponSPRAYPAINT ) DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), END_DATADESC() acttable_t CWeaponSPRAYPAINT::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, { ACT_IDLE, ACT_IDLE_SMG1, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, { ACT_WALK, ACT_WALK_RIFLE, true }, { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, // Readiness activities (not aiming) { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims // Readiness activities (aiming) { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims //End readiness activities { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, { ACT_RUN, ACT_RUN_RIFLE, true }, { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, }; IMPLEMENT_ACTTABLE(CWeaponSPRAYPAINT); //========================================================= CWeaponSPRAYPAINT::CWeaponSPRAYPAINT( ) { m_fMinRange1 = 0;// No minimum range. m_fMaxRange1 = 100; m_bAltFiresUnderwater = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::Precache( void ) { UTIL_PrecacheOther("grenade_ar2"); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: Give this weapon longer range when wielded by an ally NPC. //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::Equip( CBaseCombatCharacter *pOwner ) { if( pOwner->Classify() == CLASS_PLAYER_ALLY ) { m_fMaxRange1 = 100; } else { m_fMaxRange1 = 100; } BaseClass::Equip( pOwner ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) { // FIXME: use the returned number of bullets to account for >10hz firerate WeaponSoundRealtime( SINGLE_NPC ); CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 ); pOperator->DoMuzzleFlash(); m_iClip1 = m_iClip1 - 1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) { // Ensure we have enough rounds in the clip m_iClip1++; Vector vecShootOrigin, vecShootDir; QAngle angShootDir; GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); AngleVectors( angShootDir, &vecShootDir ); FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { switch( pEvent->event ) { case EVENT_WEAPON_SMG1: { Vector vecShootOrigin, vecShootDir; QAngle angDiscard; // Support old style attachment point firing if ((pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard))) { vecShootOrigin = pOperator->Weapon_ShootPosition(); } CAI_BaseNPC *npc = pOperator->MyNPCPointer(); ASSERT( npc != NULL ); vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); } break; /*//FIXME: Re-enable case EVENT_WEAPON_AR2_GRENADE: { CAI_BaseNPC *npc = pOperator->MyNPCPointer(); Vector vecShootOrigin, vecShootDir; vecShootOrigin = pOperator->Weapon_ShootPosition(); vecShootDir = npc->GetShootEnemyDir( vecShootOrigin ); Vector vecThrow = m_vecTossVelocity; CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc ); pGrenade->SetAbsVelocity( vecThrow ); pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) ); pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY ); pGrenade->m_hOwner = npc; pGrenade->m_pMyWeaponAR2 = this; pGrenade->SetDamage(sk_npc_dmg_ar2_grenade.GetFloat()); // FIXME: arrgg ,this is hard coded into the weapon??? m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown. m_iClip2--; } break; */ default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } //----------------------------------------------------------------------------- // Purpose: // Output : Activity //----------------------------------------------------------------------------- Activity CWeaponSPRAYPAINT::GetPrimaryAttackActivity( void ) { if ( m_nShotsFired < 2 ) return ACT_VM_PRIMARYATTACK; if ( m_nShotsFired < 3 ) return ACT_VM_RECOIL1; if ( m_nShotsFired < 4 ) return ACT_VM_RECOIL2; return ACT_VM_RECOIL3; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CWeaponSPRAYPAINT::Reload( void ) { bool fRet; float fCacheTime = m_flNextSecondaryAttack; fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { // Undo whatever the reload process has done to our secondary // attack timer. We allow you to interrupt reloading to fire // a grenade. m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime; WeaponSound( RELOAD ); } return fRet; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponSPRAYPAINT::AddViewKick( void ) { #define EASY_DAMPEN 0.5f #define MAX_VERTICAL_KICK 1.0f //Degrees #define SLIDE_LIMIT 2.0f //Seconds //Get the view kick CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( pPlayer == NULL ) return; DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT ); } #define COMBINE_MIN_GRENADE_CLEAR_DIST 256 //----------------------------------------------------------------------------- // Purpose: // Input : flDot - // flDist - // Output : int //----------------------------------------------------------------------------- int CWeaponSPRAYPAINT::WeaponRangeAttack2Condition( float flDot, float flDist ) { CAI_BaseNPC *npcOwner = GetOwner()->MyNPCPointer(); return COND_NONE; /* // -------------------------------------------------------- // Assume things haven't changed too much since last time // -------------------------------------------------------- if (gpGlobals->curtime < m_flNextGrenadeCheck ) return m_lastGrenadeCondition; */ // ----------------------- // If moving, don't check. // ----------------------- if ( npcOwner->IsMoving()) return COND_NONE; CBaseEntity *pEnemy = npcOwner->GetEnemy(); if (!pEnemy) return COND_NONE; Vector vecEnemyLKP = npcOwner->GetEnemyLKP(); if ( !( pEnemy->GetFlags() & FL_ONGROUND ) && pEnemy->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().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! return COND_NONE; } // -------------------------------------- // Get target vector // -------------------------------------- Vector vecTarget; if (random->RandomInt(0,1)) { // magically know where they are vecTarget = pEnemy->WorldSpaceCenter(); } else { // toss it to where you last saw them vecTarget = vecEnemyLKP; } // vecTarget = m_vecEnemyLKP + (pEnemy->BodyTarget( GetLocalOrigin() ) - pEnemy->GetLocalOrigin()); // estimate position // vecTarget = vecTarget + pEnemy->m_vecVelocity * 2; if ( ( vecTarget - npcOwner->GetLocalOrigin() ).Length2D() <= COMBINE_MIN_GRENADE_CLEAR_DIST ) { // crap, I don't want to blow myself up m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return (COND_NONE); } // --------------------------------------------------------------------- // Are any friendlies near the intended grenade impact area? // --------------------------------------------------------------------- CBaseEntity *pTarget = NULL; while ( ( pTarget = gEntList.FindEntityInSphere( pTarget, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST ) ) != NULL ) { //Check to see if the default relationship is hatred, and if so intensify that if ( npcOwner->IRelationType( pTarget ) == D_LI ) { // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return (COND_WEAPON_BLOCKED_BY_FRIEND); } } // --------------------------------------------------------------------- // Check that throw is legal and clear // --------------------------------------------------------------------- // FIXME: speed is based on difficulty... Vector vecToss = VecCheckThrow( this, npcOwner->GetLocalOrigin() + Vector(0,0,60), vecTarget, 600.0, 0.5 ); if ( vecToss != vec3_origin ) { m_vecTossVelocity = vecToss; // don't check again for a while. // JAY: HL1 keeps checking - test? //m_flNextGrenadeCheck = gpGlobals->curtime; m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second. return COND_CAN_RANGE_ATTACK2; } else { // don't check again for a while. m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return COND_WEAPON_SIGHT_OCCLUDED; } } //----------------------------------------------------------------------------- const WeaponProficiencyInfo_t *CWeaponSPRAYPAINT::GetProficiencyValues() { static WeaponProficiencyInfo_t proficiencyTable[] = { { 7.0, 0.75 }, { 5.00, 0.75 }, { 10.0/3.0, 0.75 }, { 5.0/3.0, 0.75 }, { 1.00, 1.0 }, }; COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); return proficiencyTable; } [/CODE] Any help is greatly appreciated.
You could just override PrimaryAttack, do the trace yourself and do a [url=https://developer.valvesoftware.com/wiki/Decal#Standard]DecalTrace[/url] to paint the decal.
Ok. I think I've got it working. Will it work or is there any errors that I'm not picking up on? [CODE]//----------------------------------------------------------------------------- // Purpose: // Output : Activity //----------------------------------------------------------------------------- Activity CWeaponSPRAYPAINT::GetPrimaryAttackActivity( void ) { if ( m_nShotsFired > 0 ) { UTIL_DecalTrace( &tr, "PaintSplash" );//TODO: Use real decal }; return ACT_VM_RECOIL3; } //-----------------------------------------------------------------------------[/CODE]
For a start "tr" doesn't exist in the scope of that function.
It would seem to me that you'll need to learn some C++ before attempting to code a Source mod. While some people have learned programming via trial and error, it's probably safer to read a proper introduction. Trial and error are still good tools, but you can get a good headstart to make more educated guesses. It will probably also improve your understanding about why code written in a certain way works, and in another won't, which will improve the code you write. I don't know what guides are current, but a bunch of years back "Learn C++ in 21 days" (the title was changed at some point, you should be able to find it though) was the book that gave me a decent introduction. "Accelerated C++" was the suggestion of other people. Make sure you get the latest revision and that it's not more than a couple of years old. The C++ tutorials I saw on the internet don't seem suitable for someone who's starting out with programming, although many are written as such, but rather people who already know one language and want an introduction to another.
Sorry, you need to Log In to post a reply to this thread.