From 2fc24c1fa3afbf7f90923564dc66c03d11cd3a6f Mon Sep 17 00:00:00 2001 From: "ALLEN-PC\\acj30" Date: Sun, 26 Jan 2025 13:42:07 -0600 Subject: [PATCH] weapon_minifabricator --- sp/src/game/client/client_ez2.vpc | 3 + .../client/ez2/c_weapon_minifabricator.cpp | 32 + .../game/client/ez2/c_weapon_minifabricator.h | 21 + .../game/server/ez2/weapon_minifabricator.cpp | 755 ++++++++++++++++++ .../game/server/ez2/weapon_minifabricator.h | 72 ++ sp/src/game/server/server_ez2.vpc | 3 + .../shared/basecombatcharacter_shared.cpp | 2 +- sp/src/game/shared/basecombatweapon_shared.h | 4 + .../shared/ez2/weapon_minifabricator_shared.h | 59 ++ sp/src/game/shared/hl2/hl2_gamerules.cpp | 5 + 10 files changed, 955 insertions(+), 1 deletion(-) create mode 100644 sp/src/game/client/ez2/c_weapon_minifabricator.cpp create mode 100644 sp/src/game/client/ez2/c_weapon_minifabricator.h create mode 100644 sp/src/game/server/ez2/weapon_minifabricator.cpp create mode 100644 sp/src/game/server/ez2/weapon_minifabricator.h create mode 100644 sp/src/game/shared/ez2/weapon_minifabricator_shared.h diff --git a/sp/src/game/client/client_ez2.vpc b/sp/src/game/client/client_ez2.vpc index b3be97355ba..8893d108e7e 100644 --- a/sp/src/game/client/client_ez2.vpc +++ b/sp/src/game/client/client_ez2.vpc @@ -44,6 +44,9 @@ $Project "Client (Entropy Zero 2)" $File "ez2\c_npc_wilson.cpp" $File "ez2\c_npc_wilson.h" $File "ez2\c_weapon_pulse_pistol.cpp" + $File "ez2\c_weapon_minifabricator.cpp" + $File "ez2\c_weapon_minifabricator.h" + $File "$SRCDIR\game\shared\ez2\weapon_minifabricator_shared.h" $File "ez2\zombiegooproxy.cpp" $File "ez2\hud_slamstatus.cpp" $File "ezu\hud_healthvialstatus.cpp" diff --git a/sp/src/game/client/ez2/c_weapon_minifabricator.cpp b/sp/src/game/client/ez2/c_weapon_minifabricator.cpp new file mode 100644 index 00000000000..70009a708e8 --- /dev/null +++ b/sp/src/game/client/ez2/c_weapon_minifabricator.cpp @@ -0,0 +1,32 @@ +//=============================================================================// +// +// Purpose: Miniature fabricator which can augment things on the spot. +// +// Author: Blixibon +// +//=============================================================================// + +#include "cbase.h" +#include "c_weapon_minifabricator.h" + + + +IMPLEMENT_CLIENTCLASS_DT( C_Weapon_MiniFabricator, DT_Weapon_MiniFabricator, CWeapon_MiniFabricator ) +END_RECV_TABLE() + +BEGIN_PREDICTION_DATA( C_Weapon_MiniFabricator ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_minifabricator, C_Weapon_MiniFabricator ) + +//----------------------------------------------------------------------------- +// Purpose: Return true if this weapon can be selected via the weapon selection +//----------------------------------------------------------------------------- +bool C_Weapon_MiniFabricator::CanBeSelected( void ) +{ + if ( !VisibleInWeaponSelection() ) + return false; + + // Can always be selected regardless of ammo + return true; +} diff --git a/sp/src/game/client/ez2/c_weapon_minifabricator.h b/sp/src/game/client/ez2/c_weapon_minifabricator.h new file mode 100644 index 00000000000..e883e2cb2ec --- /dev/null +++ b/sp/src/game/client/ez2/c_weapon_minifabricator.h @@ -0,0 +1,21 @@ +#ifndef C_Weapon_MiniFabricator_H +#define C_Weapon_MiniFabricator_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_basehlcombatweapon.h" + +class C_Weapon_MiniFabricator : public C_BaseHLCombatWeapon +{ +public: + DECLARE_CLASS( C_Weapon_MiniFabricator, C_BaseHLCombatWeapon ); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + bool CanBeSelected( void ); + + bool CanSwitchToWhileEmpty() { return true; } // Can switch to while empty +}; + +#endif // C_Weapon_MiniFabricator_H \ No newline at end of file diff --git a/sp/src/game/server/ez2/weapon_minifabricator.cpp b/sp/src/game/server/ez2/weapon_minifabricator.cpp new file mode 100644 index 00000000000..1c8828f3bab --- /dev/null +++ b/sp/src/game/server/ez2/weapon_minifabricator.cpp @@ -0,0 +1,755 @@ +//=============================================================================// +// +// Purpose: Miniature fabricator which can augment objects in the world. +// +// Author: Blixibon +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_minifabricator.h" +#include "IEffects.h" +#include "physics_prop_ragdoll.h" +#include "decals.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar sk_minifabricator_range( "sk_minifabricator_range", "96" ); + +ConVar sk_minifabricator_assimilate_min_mass( "sk_minifabricator_assimilate_min_mass", "5" ); +ConVar sk_minifabricator_assimilate_max_mass( "sk_minifabricator_assimilate_max_mass", "40" ); + +ConVar sk_minifabricator_income_prop_ratio( "sk_minifabricator_income_prop_ratio", "0.1" ); +ConVar sk_minifabricator_income_corpse_ratio( "sk_minifabricator_income_corpse_ratio", "0.04" ); +ConVar sk_minifabricator_income_weapon( "sk_minifabricator_income_weapon", "3" ); +ConVar sk_minifabricator_income_item( "sk_minifabricator_income_item", "2" ); + +ConVar g_debug_minifabricator( "g_debug_minifabricator", "0" ); + +#define MINIFABRICATOR_AUGMENTATIONS_SCRIPT "scripts/minifabricator_augmentations.txt" + +CUtlVector g_FabricatorImplantList; + +//----------------------------------------------------------------------------- +// Purpose: Ensures mini fabricator augmentations are loaded and precached when they are needed. +// This is based on the Xen Grenade recipe manager, but it's much simpler. +//----------------------------------------------------------------------------- +class CMiniFabricatorAugmentationManager : public CAutoGameSystem +{ +public: + CMiniFabricatorAugmentationManager() : CAutoGameSystem( "CMiniFabricatorAugmentationManager" ) + { + } + + virtual bool Init() + { + return true; + } + + virtual void LevelInitPreEntity() + { + // Unless cheats are on, assume the fabricator doesn't exist by default + if (sv_cheats->GetBool()) + { + m_bFabricatorExists = true; + m_pszFabricreator = "sv_cheats"; + } + else + { + m_bFabricatorExists = false; + m_pszFabricreator = "Unknown"; // Default value + } + + m_bFabricatorPrecached = false; + + m_bPrecachedLate = false; + } + + virtual void LevelInitPostEntity() + { + // If Xen exists, precache Xen + if (m_bFabricatorExists) + { + PrecacheMiniFabricatorAugmentations(); + m_bFabricatorPrecached = true; + } + } + + virtual void LevelShutdownPreEntity() + { + } + + virtual void LevelShutdownPostEntity() + { + } + + virtual void OnRestore() + { + if (m_bFabricatorExists) + { + PrecacheMiniFabricatorAugmentations(); + } + } + + //------------------------------------------------------------------------------------ + + inline bool FabricatorExists() const { return m_bFabricatorExists; } + + inline bool PrecachedLate() const { return m_bPrecachedLate; } + inline const char *GetFabricatorCreator() const { return m_pszFabricreator; } + + // Affirms the existence of the fabricator + void VerifyAugmentationManager( const char *pszActivator ) + { + if (!m_bFabricatorExists) + { + Msg( "Mini-fabricator augmentations manager activated\n" ); + m_pszFabricreator = pszActivator; + } + + m_bFabricatorExists = true; + + if (!m_bFabricatorPrecached && gpGlobals->curtime > 2.0f) + { + PrecacheMiniFabricatorAugmentations(); + + Msg( "Late precache of mini-fabricator augmentations\n" ); + + m_bPrecachedLate = true; + m_bFabricatorPrecached = true; + } + } + + void PrecacheMiniFabricatorAugmentations(); + + const MiniFabricatorAugmentation_t *FindAugmentationForEntity( CBaseEntity *pTarget, CWeapon_MiniFabricator *pFabricator, CBaseCombatCharacter *pOwner ); + const MiniFabricatorAugmentation_t *FindAugmentationByName( const char *pszName ); + +protected: + bool m_bFabricatorExists; + bool m_bFabricatorPrecached; + + // The one who precached the fabricator augmentations + const char *m_pszFabricreator; + + bool m_bPrecachedLate; + + CUtlVector m_Augmentations; +}; + +CMiniFabricatorAugmentationManager g_MiniFabricatorAugmentationManager; + +//----------------------------------------------------------------------------- +// Purpose: Debug commands +//----------------------------------------------------------------------------- +CON_COMMAND( minifabricator_print_state, "Prints the augmentation manager state." ) +{ + char szMsg[512]; + if (g_MiniFabricatorAugmentationManager.FabricatorExists()) + { + Q_strncpy( szMsg, "Fabricator exists.\n{\n", sizeof( szMsg ) ); + + Q_snprintf( szMsg, sizeof( szMsg ), "%s Precacher: %s\n", szMsg, g_MiniFabricatorAugmentationManager.GetFabricatorCreator() ); + Q_snprintf( szMsg, sizeof( szMsg ), "%s Precached late: %s\n", szMsg, g_MiniFabricatorAugmentationManager.PrecachedLate() ? "Yes" : "No" ); + + Q_snprintf( szMsg, sizeof( szMsg ), "%s}\n", szMsg ); + } + else + { + Q_strncpy( szMsg, "Fabricator does not exist.\n", sizeof( szMsg ) ); + } + + Msg( "%s", szMsg ); +} + +//----------------------------------------------------------------------------- +// Purpose: Precaches mini fabricator augmentations +//----------------------------------------------------------------------------- +void CMiniFabricatorAugmentationManager::PrecacheMiniFabricatorAugmentations( void ) +{ + if ( m_bFabricatorPrecached ) + return; + + KeyValues *pAugmentationsFile = new KeyValues( "MiniFabricatorAugmentations" ); + if (pAugmentationsFile->LoadFromFile( filesystem, MINIFABRICATOR_AUGMENTATIONS_SCRIPT )) + { + for (KeyValues *pAugmentation = pAugmentationsFile->GetFirstSubKey(); pAugmentation != NULL; pAugmentation = pAugmentation->GetNextKey()) + { + int i = m_Augmentations.AddToTail(); + m_Augmentations[i].pszName = STRING( AllocPooledString( pAugmentation->GetName() ) ); + + for (KeyValues *pSubKey = pAugmentation->GetFirstSubKey(); pSubKey != NULL; pSubKey = pSubKey->GetNextKey()) + { + const char *pszKeyName = pSubKey->GetName(); + if (V_strnicmp( pszKeyName, "filter_", 7 ) == 0) + { + m_Augmentations[i].pszFilter = STRING( AllocPooledString( pSubKey->GetString() ) ); + pszKeyName += 7; + + // Determine filter type + if (FStrEq( pszKeyName, "classname" )) + m_Augmentations[i].nFilterType = MiniFabricatorAugmentation_t::FILTER_CLASSNAME; + else if (FStrEq( pszKeyName, "propinter" )) + m_Augmentations[i].nFilterType = MiniFabricatorAugmentation_t::FILTER_PROPINT; + } + else if (FStrEq( pszKeyName, "model" )) + { + m_Augmentations[i].pszModel = AllocPooledString( pSubKey->GetString() ); + + // Precache the model + UTIL_PrecacheOther( "prop_fabricator_implant", STRING( m_Augmentations[i].pszModel ) ); + } + else if (FStrEq( pszKeyName, "skin" )) + { + m_Augmentations[i].nSkin = pSubKey->GetInt(); + } + else if (FStrEq( pszKeyName, "attachment" )) + { + m_Augmentations[i].pszAttachment = STRING( AllocPooledString( pSubKey->GetString() ) ); + + if (m_Augmentations[i].nFollowType != MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_BONEMERGE) + m_Augmentations[i].nFollowType = MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_ATTACHMENT; + } + else if (FStrEq( pszKeyName, "bonemerge" ) && pSubKey->GetBool() == true) + { + m_Augmentations[i].nFollowType = MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_BONEMERGE; + } + else if (FStrEq( pszKeyName, "push_scale" )) + { + m_Augmentations[i].flPushScale = pSubKey->GetFloat(); + } + else if (FStrEq( pszKeyName, "vscript_file" )) + { + m_Augmentations[i].pszVScriptFile = STRING( AllocPooledString( pSubKey->GetString() ) ); + } + else if (FStrEq( pszKeyName, "cost" )) + { + m_Augmentations[i].nCost = pSubKey->GetInt(); + } + } + } + } + + pAugmentationsFile->deleteThis(); + + if (g_debug_minifabricator.GetBool()) + { + Msg( "Precached %i augmentations\n", m_Augmentations.Count() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a matching mini fabricator augmentation +//----------------------------------------------------------------------------- +const MiniFabricatorAugmentation_t *CMiniFabricatorAugmentationManager::FindAugmentationForEntity( CBaseEntity *pTarget, CWeapon_MiniFabricator *pFabricator, CBaseCombatCharacter *pOwner ) +{ + if (pTarget == NULL) + return NULL; + + for (int i = 0; i < m_Augmentations.Count(); i++) + { + bool bPassed = false; + switch (m_Augmentations[i].nFilterType) + { + case MiniFabricatorAugmentation_t::FILTER_CLASSNAME: + bPassed = pTarget->ClassMatches( m_Augmentations[i].pszFilter ); + break; + + case MiniFabricatorAugmentation_t::FILTER_PROPINT: + { + if (V_strncmp( pTarget->GetClassname(), "prop_", 5 ) != 0) + break; + + // TODO: Would all classes starting with "prop_" be descended from CBreakableProp? + CBreakableProp *pProp = dynamic_cast(pTarget); + if (pProp) + { + if (FStrEq( m_Augmentations[i].pszFilter, "explosive" )) + { + bPassed = pProp->HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) || + pProp->HasInteraction( PROPINTER_FIRE_FLAMMABLE ) || + pProp->GetExplosiveRadius() > 0 || pProp->GetExplosiveDamage() > 0; + } + } + break; + } + } + + if (bPassed) + { + if (g_debug_minifabricator.GetBool()) + { + Msg( "Found augmentation %s for %s\n", m_Augmentations[i].pszName, pTarget->GetDebugName() ); + } + return &m_Augmentations[i]; + } + } + + if (g_debug_minifabricator.GetBool()) + { + Msg( "No augmentation found for %s\n", pTarget->GetDebugName() ); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a mini fabricator augmentation by name +//----------------------------------------------------------------------------- +const MiniFabricatorAugmentation_t *CMiniFabricatorAugmentationManager::FindAugmentationByName( const char *pszName ) +{ + for (int i = 0; i < m_Augmentations.Count(); i++) + { + if (FStrEq( m_Augmentations[i].pszName, pszName )) + return &m_Augmentations[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- + +IMPLEMENT_SERVERCLASS_ST( CWeapon_MiniFabricator, DT_Weapon_MiniFabricator ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( weapon_minifabricator, CWeapon_MiniFabricator ); +//PRECACHE_WEAPON_REGISTER( weapon_minifabricator ); + +BEGIN_DATADESC( CWeapon_MiniFabricator ) + DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecTargetEndPos, FIELD_VECTOR ), + DEFINE_FIELD( m_vecTargetNormal, FIELD_VECTOR ), + + DEFINE_FIELD( m_flNextTargetCheckTime, FIELD_TIME ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Precache +//----------------------------------------------------------------------------- +void CWeapon_MiniFabricator::Precache( void ) +{ + BaseClass::Precache(); + + g_MiniFabricatorAugmentationManager.VerifyAugmentationManager( GetClassname() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeapon_MiniFabricator::ItemPreFrame( void ) +{ + BaseClass::ItemPreFrame(); + + if (m_flNextTargetCheckTime < gpGlobals->curtime && GetOwner()) + { + // Check for a target entity every interval + CheckForTarget( GetOwner() ); + m_flNextTargetCheckTime = gpGlobals->curtime + 0.25f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the target entity +//----------------------------------------------------------------------------- +CBaseEntity *CWeapon_MiniFabricator::FindTarget( CBaseCombatCharacter *pOwner, Vector &vecEndPos, Vector &vecNormal ) +{ + trace_t tr; + CTraceFilterSkipTwoEntities traceFilter( this, pOwner, COLLISION_GROUP_NONE ); + UTIL_TraceLine( pOwner->EyePosition(), pOwner->EyePosition() + (pOwner->EyeDirection3D() * sk_minifabricator_range.GetFloat()), MASK_SHOT_HULL, &traceFilter, &tr ); + + if (tr.m_pEnt) + { + if (tr.m_pEnt->IsWorld()) + return NULL; + + // For now, pretend "displacement impossible" also means it's impossible to augment + if (tr.m_pEnt->IsDisplacementImpossible()) + return NULL; + } + else + return NULL; + + vecEndPos = tr.endpos; + vecNormal = tr.plane.normal; + return tr.m_pEnt; +} + +//----------------------------------------------------------------------------- +// Purpose: Assigns the target entity and augmentation +//----------------------------------------------------------------------------- +void CWeapon_MiniFabricator::CheckForTarget( CBaseCombatCharacter *pOwner ) +{ + CBaseEntity *pTarget = FindTarget( pOwner, m_vecTargetEndPos, m_vecTargetNormal ); + + // Skip augmentation check if it's the same target we already have + if (m_hTargetEnt != pTarget) + { + // Get appropriate augmentation + m_pTargetAugmentation = g_MiniFabricatorAugmentationManager.FindAugmentationForEntity( pTarget, this, pOwner ); + } + + m_hTargetEnt = pTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: Main attack +//----------------------------------------------------------------------------- +void CWeapon_MiniFabricator::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (!pPlayer) + { + return; + } + + pPlayer->m_flNextAttack = gpGlobals->curtime + 1.0f; + + // Get our target entity + if (!m_hTargetEnt) + CheckForTarget( pPlayer ); + + if (!m_hTargetEnt || !m_pTargetAugmentation || m_pTargetAugmentation->nCost > pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + { + if (g_debug_minifabricator.GetBool()) + { + if (!m_hTargetEnt) + Msg( "Mini fabricator: No target\n" ); + else if (!m_pTargetAugmentation) + Msg( "Mini fabricator: No target augmentation\n" ); + else if (m_pTargetAugmentation->nCost > pPlayer->GetAmmoCount( m_iPrimaryAmmoType )) + Msg( "Mini fabricator: Too expensive\n" ); + } + + WeaponSound( EMPTY ); + return; + } + + // Make sure there's not already an augmentation + for (int i = 0; i < g_FabricatorImplantList.Count(); i++) + { + if (g_FabricatorImplantList[i] && g_FabricatorImplantList[i]->GetOwnerEntity() == m_hTargetEnt) + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Target already augmented\n" ); + WeaponSound( EMPTY ); + return; + } + } + + // Augment the thing + { + Vector vecImplantOrigin = vec3_origin; + QAngle angImplantAngles = vec3_angle; + + // Create the implant + CFabricatorImplant *pImplant = static_cast(CreateNoSpawn( "prop_fabricator_implant", vecImplantOrigin, angImplantAngles, m_hTargetEnt )); + if (pImplant) + { + pImplant->SetModelName( m_pTargetAugmentation->pszModel ); + pImplant->m_nSkin = m_pTargetAugmentation->nSkin; + + switch (m_pTargetAugmentation->nFollowType) + { + case MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_FROM_TRACE: + { + // Use the trace position + vecImplantOrigin = m_vecTargetEndPos; + VectorAngles( m_vecTargetNormal, angImplantAngles ); + pImplant->SetParent( m_hTargetEnt, -1 ); + } + break; + case MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_BONEMERGE: + pImplant->FollowEntity( m_hTargetEnt, true ); + // Fall through + case MiniFabricatorAugmentation_t::IMPLANT_FOLLOW_ATTACHMENT: + if (m_hTargetEnt->GetBaseAnimating() && m_pTargetAugmentation->pszAttachment != NULL) + { + // Use the attachment + int nAttachment = m_hTargetEnt->GetBaseAnimating()->LookupAttachment( m_pTargetAugmentation->pszAttachment ); + + m_hTargetEnt->GetBaseAnimating()->GetAttachment( m_pTargetAugmentation->pszAttachment, vecImplantOrigin, angImplantAngles ); + + pImplant->SetParent( m_hTargetEnt, nAttachment ); + } + break; + } + + if (vecImplantOrigin == vec3_origin) + { + Warning( "WARNING: Cannot resolve augmentation \"%s\" position for %s\n", m_pTargetAugmentation->pszName, m_hTargetEnt->GetDebugName() ); + } + + pImplant->SetAbsOrigin( vecImplantOrigin ); + pImplant->SetAbsAngles( angImplantAngles ); + + pImplant->InitFabricationData( this, pPlayer, m_pTargetAugmentation ); + + DispatchSpawn( pImplant ); + } + else + { + Warning( "ERROR: No implant created for augmentation %s on entity %s", m_pTargetAugmentation->pszName, m_hTargetEnt->GetDebugName() ); + return; + } + + // Bump the entity slightly + if (m_pTargetAugmentation->flPushScale > 0.0f) + m_hTargetEnt->ApplyAbsVelocityImpulse( m_vecTargetNormal * m_pTargetAugmentation->flPushScale ); + + g_pEffects->Sparks( vecImplantOrigin, 1, 2, &m_vecTargetNormal ); + UTIL_Smoke( vecImplantOrigin, random->RandomInt( 5, 10 ), 10 ); + + // Run the script + if (m_hTargetEnt->ValidateScriptScope()) + { + g_pScriptVM->SetValue( m_hTargetEnt->GetScriptScope(), "m_hImplant", ToHScript( pImplant ) ); + g_pScriptVM->SetValue( m_hTargetEnt->GetScriptScope(), "m_hFabricatorUser", ToHScript( pPlayer ) ); + m_hTargetEnt->RunScriptFile( m_pTargetAugmentation->pszVScriptFile ); + } + else + { + Warning( "ERROR: Script scope failed to validate on %s, augmentation failed\n", m_hTargetEnt->GetDebugName() ); + } + + // Apply the cost + pPlayer->RemoveAmmo( m_pTargetAugmentation->nCost, m_iPrimaryAmmoType ); + + WeaponSound( SINGLE ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Secondary attack - assimilate object +//----------------------------------------------------------------------------- +void CWeapon_MiniFabricator::SecondaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (!pPlayer) + { + return; + } + + pPlayer->m_flNextAttack = gpGlobals->curtime + 1.0f; + + // Get our target entity + if (!m_hTargetEnt) + CheckForTarget( pPlayer ); + + if (!m_hTargetEnt) + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: No target\n" ); + WeaponSound( EMPTY ); + return; + } + + extern ConVar sk_plr_dmg_resin; + CTakeDamageInfo info( this, pPlayer, sk_plr_dmg_resin.GetFloat(), DMG_SHOCK ); + + // See if any augmentations are connected to this and we can retract it + for (int i = 0; i < g_FabricatorImplantList.Count(); i++) + { + if (g_FabricatorImplantList[i] && g_FabricatorImplantList[i]->GetOwnerEntity() == m_hTargetEnt) + { + // Find this implant's augmentation + const MiniFabricatorAugmentation_t *pAugmentation = g_MiniFabricatorAugmentationManager.FindAugmentationByName( g_FabricatorImplantList[i]->GetAugmentationName() ); + + // Compensate with half of the cost being given back + pPlayer->GiveAmmo( pAugmentation->nCost / 2, m_iPrimaryAmmoType ); + + // Break the implant (handles stuff like removing the code automatically) + g_FabricatorImplantList[i]->Break( pPlayer, info ); + + // Bump the entity slightly + if (m_pTargetAugmentation->flPushScale > 0.0f) + m_hTargetEnt->ApplyAbsVelocityImpulse( -m_vecTargetNormal * m_pTargetAugmentation->flPushScale ); + return; + } + } + + // If we're already full on resin, don't do anything + if (!g_pGameRules->CanHaveAmmo( pPlayer, m_iPrimaryAmmoType )) + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Resin full\n" ); + WeaponSound( EMPTY ); + return; + } + + // If the target can't take damage from us, don't do anything + if (!m_hTargetEnt->PassesDamageFilter( info )) + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Doesn't pass damage filter\n" ); + WeaponSound( EMPTY ); + return; + } + + Vector vecTargetCenter = m_hTargetEnt->WorldSpaceCenter(); + + // See if this is something we can assimilate + if (m_hTargetEnt->IsNPC()) + { + // If it's a Combine unit, just damage it slightly instead + if ( m_hTargetEnt->Classify() == CLASS_COMBINE || + m_hTargetEnt->Classify() == CLASS_METROPOLICE || + m_hTargetEnt->Classify() == CLASS_MANHACK || + m_hTargetEnt->Classify() == CLASS_SCANNER || + m_hTargetEnt->Classify() == CLASS_COMBINE_HUSK ) + { + m_hTargetEnt->TakeDamage( info ); + } + else + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Not a Combine NPC\n" ); + WeaponSound( EMPTY ); + return; + } + } + else if ( m_hTargetEnt->ClassMatches( "prop_ragdoll" ) ) + { + // Can assimilate Combine corpses + CRagdollProp *pRagdoll = static_cast(m_hTargetEnt.Get()); + if (pRagdoll->GetSourceClassification() == CLASS_COMBINE || + pRagdoll->GetSourceClassification() == CLASS_METROPOLICE || + pRagdoll->GetSourceClassification() == CLASS_COMBINE_HUSK) + { + float flMass = 0.0f; + for ( int i = 0; i < pRagdoll->GetRagdoll()->listCount; i++) + { + if ( pRagdoll->GetRagdoll()->list[i].pObject != NULL ) + { + flMass += pRagdoll->GetRagdoll()->list[i].pObject->GetMass(); + } + } + pPlayer->GiveAmmo( sk_minifabricator_income_corpse_ratio.GetFloat() * flMass, m_iPrimaryAmmoType ); + + info.SetDamage( pRagdoll->GetHealth() ); + info.AddDamageType( DMG_ALWAYSGIB ); + pRagdoll->TakeDamage( info ); + } + else + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Not a Combine ragdoll\n" ); + WeaponSound( EMPTY ); + return; + } + } + else if ( m_hTargetEnt->IsBaseCombatWeapon() ) + { + // TODO + } + else if ( m_hTargetEnt->IsCombatItem() ) + { + // TODO + } + else if ( m_hTargetEnt->ClassMatches( "prop_physics" ) ) // For now, do physics props only + { + // Check if it's a small metal object we can assimilate + if (m_hTargetEnt->VPhysicsGetObject()) + { + const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( m_hTargetEnt->VPhysicsGetObject()->GetMaterialIndex() ); + float flMass = m_hTargetEnt->VPhysicsGetObject()->GetMass(); + if (pSurfaceData->game.material == CHAR_TEX_METAL && flMass >= sk_minifabricator_assimilate_min_mass.GetFloat() && flMass <= sk_minifabricator_assimilate_max_mass.GetFloat()) + { + // Round to nearest int + pPlayer->GiveAmmo( roundf( sk_minifabricator_income_prop_ratio.GetFloat() * flMass ), m_iPrimaryAmmoType ); + + info.SetDamage( m_hTargetEnt->GetHealth() ); + CBreakableProp *pProp = static_cast(m_hTargetEnt.Get()); + if (pProp) + pProp->Break( pPlayer, info ); + } + else + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: Not metal or not within mass range\n" ); + WeaponSound( EMPTY ); + return; + } + } + else + { + if (g_debug_minifabricator.GetBool()) + Msg( "Mini fabricator: No VPhysics\n" ); + WeaponSound( EMPTY ); + return; + } + } + + g_pEffects->Sparks( m_vecTargetEndPos, 2, 1 ); + UTIL_Smoke( m_vecTargetEndPos, random->RandomInt( 5, 10 ), 10 ); + + QAngle angGibAngles; + VectorAngles( m_vecTargetNormal, angGibAngles ); + + if (!m_hTargetEnt || m_hTargetEnt->GetHealth() <= 0) + { + // Throw out some small chunks + CPVSFilter filter( vecTargetCenter ); + for (int i = 0; i < 4; i++) + { + Vector gibVelocity = RandomVector( -100, 100 ); + int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) ); + te->BreakModel( filter, 0.0, vecTargetCenter, angGibAngles, Vector( 40, 40, 40 ), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL ); + } + } + + WeaponSound( WPN_DOUBLE ); +} + +//----------------------------------------------------------------------------- + +LINK_ENTITY_TO_CLASS( prop_fabricator_implant, CFabricatorImplant ); + +BEGIN_DATADESC( CFabricatorImplant ) + DEFINE_FIELD( m_hFabricator, FIELD_EHANDLE ), + DEFINE_FIELD( m_hFabricatorOwner, FIELD_EHANDLE ), + DEFINE_FIELD( m_iszAugmentationName, FIELD_STRING ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFabricatorImplant::CFabricatorImplant() +{ + g_FabricatorImplantList.AddToTail( this ); +} + +CFabricatorImplant::~CFabricatorImplant() +{ + g_FabricatorImplantList.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFabricatorImplant::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ) +{ + // Mask the effect with some sparks + g_pEffects->Sparks( GetAbsOrigin(), 2, 1); + UTIL_Smoke( GetAbsOrigin(), random->RandomInt( 5, 10 ), 10 ); + + if (GetOwnerEntity()) + { + // Tell the script to remove itself + GetOwnerEntity()->RunScript("RemoveAugmentation()"); + } + + BaseClass::Break( pBreaker, info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFabricatorImplant::InitFabricationData( CWeapon_MiniFabricator *pFabricator, CBaseCombatCharacter *pOwner, const MiniFabricatorAugmentation_t *pAugmentation ) +{ + m_hFabricator = pFabricator; + m_hFabricatorOwner = pOwner; + m_iszAugmentationName = AllocPooledString( pAugmentation->pszName ); +} diff --git a/sp/src/game/server/ez2/weapon_minifabricator.h b/sp/src/game/server/ez2/weapon_minifabricator.h new file mode 100644 index 00000000000..6766fd7f8c6 --- /dev/null +++ b/sp/src/game/server/ez2/weapon_minifabricator.h @@ -0,0 +1,72 @@ +//=============================================================================// +// +// Purpose: Miniature fabricator which can augment things on the spot. +// +// Author: Blixibon +// +//=============================================================================// + +#include "basehlcombatweapon.h" +#include "ez2/weapon_minifabricator_shared.h" +#include "props.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWeapon_MiniFabricator : public CBaseHLCombatWeapon +{ +public: + DECLARE_CLASS( CWeapon_MiniFabricator, CBaseHLCombatWeapon ); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + void Precache( void ); + + void ItemPreFrame( void ); + CBaseEntity* FindTarget( CBaseCombatCharacter *pOwner, Vector &vecEndPos, Vector &vecNormal ); + void CheckForTarget( CBaseCombatCharacter *pOwner ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + + bool UsesClipsForAmmo1( void ) const { return false; } // Don't use clips! + + bool CanSwitchToWhileEmpty() { return true; } // Can switch to while empty + + bool ShouldDisplayHUDHint() { return true; } // This weapon will need some explanation + +private: + + EHANDLE m_hTargetEnt; // The entity the player is currently looking at + + const MiniFabricatorAugmentation_t *m_pTargetAugmentation; // The augmentation which can be used by the target entity (if valid) + Vector m_vecTargetEndPos; // The trace end position for the target + Vector m_vecTargetNormal; // The trace normal for the target + + float m_flNextTargetCheckTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CFabricatorImplant : public CDynamicProp +{ + DECLARE_CLASS( CFabricatorImplant, CDynamicProp ); + DECLARE_DATADESC(); +public: + CFabricatorImplant(); + ~CFabricatorImplant(); + + void Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ); + + void InitFabricationData( CWeapon_MiniFabricator *pFabricator, CBaseCombatCharacter *pOwner, const MiniFabricatorAugmentation_t *pAugmentation ); + + CWeapon_MiniFabricator* GetFabricator() const { return m_hFabricator; } + CBaseCombatCharacter* GetFabricatorOwner() const { return m_hFabricatorOwner; } + const char* GetAugmentationName() const { return STRING( m_iszAugmentationName ); } + +private: + CHandle m_hFabricator; + CHandle m_hFabricatorOwner; + string_t m_iszAugmentationName; +}; \ No newline at end of file diff --git a/sp/src/game/server/server_ez2.vpc b/sp/src/game/server/server_ez2.vpc index 4f0a0e06ca2..8fed8a804ad 100644 --- a/sp/src/game/server/server_ez2.vpc +++ b/sp/src/game/server/server_ez2.vpc @@ -92,6 +92,9 @@ $Project "Server (Entropy Zero 2)" $File "ez2\weapon_oicw.cpp" $File "ez2\weapon_displacer_pistol.h" $File "ez2\weapon_displacer_pistol.cpp" + $File "ez2\weapon_minifabricator.h" + $File "ez2\weapon_minifabricator.cpp" + $File "$SRCDIR\game\shared\ez2\weapon_minifabricator_shared.h" } $File "ai_eventresponse.cpp" $File "ai_eventresponse.h" diff --git a/sp/src/game/shared/basecombatcharacter_shared.cpp b/sp/src/game/shared/basecombatcharacter_shared.cpp index a109ec9662b..af8041d5769 100644 --- a/sp/src/game/shared/basecombatcharacter_shared.cpp +++ b/sp/src/game/shared/basecombatcharacter_shared.cpp @@ -85,7 +85,7 @@ bool CBaseCombatCharacter::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) #ifdef MAPBASE if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) && !pWeapon->HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) #ifdef EZ - && GetActiveWeapon() != NULL && GetActiveWeapon() != pWeapon + && GetActiveWeapon() != NULL && GetActiveWeapon() != pWeapon && !pWeapon->CanSwitchToWhileEmpty() #endif ) #else diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index f807d6e3119..6af21d86f04 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -294,6 +294,10 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM virtual bool IsHolstered(){ return false; } virtual void Detach() {} +#ifdef EZ + virtual bool CanSwitchToWhileEmpty() { return false; } +#endif + // Weapon behaviour virtual void ItemPreFrame( void ); // called each frame by the player PreThink virtual void ItemPostFrame( void ); // called each frame by the player PostThink diff --git a/sp/src/game/shared/ez2/weapon_minifabricator_shared.h b/sp/src/game/shared/ez2/weapon_minifabricator_shared.h new file mode 100644 index 00000000000..558bd38ae7a --- /dev/null +++ b/sp/src/game/shared/ez2/weapon_minifabricator_shared.h @@ -0,0 +1,59 @@ +//=============================================================================// +// +// Purpose: Base mini fabricator values. +// +// Author: Blixibon +// +//=============================================================================// + +#ifndef WEAPON_MINIFABRICATOR_SHARED_H +#define WEAPON_MINIFABRICATOR_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + +struct MiniFabricatorAugmentation_t +{ + // Used mainly for debugging + const char *pszName; + + enum + { + FILTER_CLASSNAME, // Direct classname + FILTER_PROPINT, // CBreakableProp interation + }; + + const char *pszFilter; + int nFilterType; + + string_t pszModel; + int nSkin; + + enum + { + IMPLANT_FOLLOW_FROM_TRACE, // Puts implant at trace position and parents it (default) + IMPLANT_FOLLOW_ATTACHMENT, // Parents implant to specific attachment + IMPLANT_FOLLOW_BONEMERGE, // Bonemerges the implant with the target + }; + + const char *pszAttachment; + int nFollowType; + + // How much to push the entity when applying or removing the augmentation + float flPushScale = 2.0f; + + const char *pszVScriptFile; + + int nCost; +}; + +//enum MiniFabricatorAbilities_t +//{ +// MINIFABRICATOR_ABILITY_CONNECT, // Connects two objects +// MINIFABRICATOR_ABILITY_AUGMENT, // Augments something +// +// NUM_MINIFABRICATOR_ABILITIES, +// LAST_MINIFABRICATOR_ABILITY = NUM_MINIFABRICATOR_ABILITIES-1, +//}; + +#endif // WEAPON_MINIFABRICATOR_SHARED_H diff --git a/sp/src/game/shared/hl2/hl2_gamerules.cpp b/sp/src/game/shared/hl2/hl2_gamerules.cpp index f3397dbaa10..0c624f56ebf 100644 --- a/sp/src/game/shared/hl2/hl2_gamerules.cpp +++ b/sp/src/game/shared/hl2/hl2_gamerules.cpp @@ -399,6 +399,10 @@ ConVar sk_npc_dmg_gunship_to_plr ( "sk_npc_dmg_gunship_to_plr", "0", FCVAR_REPLI ConVar sv_player_death_time( "sv_player_death_time", "1.0", FCVAR_REPLICATED ); ConVar sk_max_stasis_grenade ( "sk_max_stasis_grenade", "5", FCVAR_REPLICATED ); + +ConVar sk_plr_dmg_resin( "sk_plr_dmg_resin", "5", FCVAR_REPLICATED ); +ConVar sk_npc_dmg_resin( "sk_npc_dmg_resin", "5", FCVAR_REPLICATED ); +ConVar sk_max_resin( "sk_max_resin", "24", FCVAR_REPLICATED ); #endif #ifdef CSS_WEAPONS_IN_HL2 @@ -2900,6 +2904,7 @@ CAmmoDef *GetAmmoDef() // Entropy Zero 2 def.AddAmmoType("XenGrenade", DMG_BLAST, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_hopwire", 0, 0); // this is the xen grenade def.AddAmmoType("StasisGrenade", DMG_BLAST, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_stasis_grenade", 0, 0); + def.AddAmmoType("Resin", DMG_CLUB, TRACER_NONE, "sk_plr_dmg_resin", "sk_npc_dmg_resin", "sk_max_resin", 0, 0); // Entropy Zero 2 def.AddAmmoType("GaussPistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_gauss_pistol", "sk_npc_dmg_gauss_pistol", "sk_max_gauss_pistol", BULLET_IMPULSE(200, 1225), 0);