diff --git a/game/client/client_hl2mp.vpc b/game/client/client_hl2mp.vpc index 88e18a8e..aaa6c615 100644 --- a/game/client/client_hl2mp.vpc +++ b/game/client/client_hl2mp.vpc @@ -14,7 +14,7 @@ $Configuration { $Compiler { - $AdditionalIncludeDirectories "$BASE;hl2mp\ui,.\hl2mp,$SRCDIR\game\shared\hl2mp,.\hl2,.\hl2\elements,$SRCDIR\game\shared\hl2" + $AdditionalIncludeDirectories "$BASE;hl2mp\ui,.\hl2mp,$SRCDIR\game\shared\hl2mp,.\hl2,.\hl2\elements,$SRCDIR\game\shared\hl2,$SRCDIR\game\shared\stargate" $PreprocessorDefinitions "$BASE;HL2MP;HL2_CLIENT_DLL" } } @@ -170,5 +170,11 @@ $Project "Client (HL2MP)" $File "hl2mp\hud_deathnotice.cpp" } } + + $Folder "Stargate" + { + $File "$SRCDIR\game\shared\stargate\sg_weapons.cpp" + $File "$SRCDIR\game\shared\stargate\sg_constants.h" + } } } diff --git a/game/server/hl2mp/hl2mp_player.cpp b/game/server/hl2mp/hl2mp_player.cpp index 1469d842..bc1412ac 100644 --- a/game/server/hl2mp/hl2mp_player.cpp +++ b/game/server/hl2mp/hl2mp_player.cpp @@ -1321,19 +1321,44 @@ CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) { if ( GetTeamNumber() == TEAM_COMBINE ) { - pSpawnpointName = "info_player_combine"; + pSpawnpointName = "info_player_torri"; pLastSpawnPoint = g_pLastCombineSpawn; } else if ( GetTeamNumber() == TEAM_REBELS ) { - pSpawnpointName = "info_player_rebel"; + pSpawnpointName = "info_player_goauld"; pLastSpawnPoint = g_pLastRebelSpawn; } + else if ( GetTeamNumber() == TEAM_JAFFA ) + { + pSpawnpointName = "info_player_marine"; + pLastSpawnPoint = g_pLastSpawn; + } + else if ( GetTeamNumber() == TEAM_UNAS ) + { + pSpawnpointName = "info_player_unas"; + pLastSpawnPoint = g_pLastSpawn; + } if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) { - pSpawnpointName = "info_player_deathmatch"; - pLastSpawnPoint = g_pLastSpawn; + if ( GetTeamNumber() == TEAM_COMBINE && gEntList.FindEntityByClassname( NULL, "info_player_marine" ) != NULL ) + { + pSpawnpointName = "info_player_marine"; + } + else if ( GetTeamNumber() == TEAM_COMBINE && gEntList.FindEntityByClassname( NULL, "info_player_combine" ) != NULL ) + { + pSpawnpointName = "info_player_combine"; + } + else if ( GetTeamNumber() == TEAM_REBELS && gEntList.FindEntityByClassname( NULL, "info_player_rebel" ) != NULL ) + { + pSpawnpointName = "info_player_rebel"; + } + else + { + pSpawnpointName = "info_player_deathmatch"; + pLastSpawnPoint = g_pLastSpawn; + } } } diff --git a/game/server/server_hl2mp.vpc b/game/server/server_hl2mp.vpc index 46d039db..e101a4d2 100644 --- a/game/server/server_hl2mp.vpc +++ b/game/server/server_hl2mp.vpc @@ -15,7 +15,7 @@ $Configuration { $Compiler { - $AdditionalIncludeDirectories "$BASE;$SRCDIR\game\shared\hl2,.\hl2,.\hl2mp,$SRCDIR\game\shared\hl2mp" + $AdditionalIncludeDirectories "$BASE;$SRCDIR\game\shared\hl2,.\hl2,.\hl2mp,$SRCDIR\game\shared\hl2mp,.\stargate,$SRCDIR\game\shared\stargate" $PreprocessorDefinitions "$BASE;HL2MP;HL2_DLL" } } @@ -292,5 +292,20 @@ $Project "Server (HL2MP)" $File "$SRCDIR\game\shared\hl2mp\weapon_stunstick.cpp" } } + + $Folder "Stargate" + { + $File "stargate\dhd_console.cpp" + $File "stargate\gate_travel.cpp" + $File "stargate\multi_gate.cpp" + $File "stargate\recovered_entities.cpp" + $File "stargate\ring_transporter.cpp" + $File "stargate\sg_event.cpp" + $File "stargate\sg_kawoosh.cpp" + $File "stargate\sg_player_spawn.cpp" + $File "stargate\STUBS.cpp" + $File "$SRCDIR\game\shared\stargate\sg_weapons.cpp" + $File "$SRCDIR\game\shared\stargate\sg_constants.h" + } } } diff --git a/game/server/stargate/STUBS.cpp b/game/server/stargate/STUBS.cpp new file mode 100644 index 00000000..a03d3152 --- /dev/null +++ b/game/server/stargate/STUBS.cpp @@ -0,0 +1,38 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// One-file stubs for the remaining preserved entities. Each just registers its +// classname against CBaseEntity so the engine accepts maps that reference them +// (otherwise vbsp/spawn would log "Spawnfunction for not found"). +// +// Replace these one by one as gameplay is ported — copy a stub out into its +// own file and flesh it out following the dhd_console.cpp / sg_event.cpp +// pattern. +// +//============================================================================== +#include "cbase.h" + +#define STUB_ENTITY(classname) \ + class C##classname : public CBaseAnimating \ + { \ + public: \ + DECLARE_CLASS( C##classname, CBaseAnimating ); \ + void Spawn() override \ + { \ + BaseClass::Spawn(); \ + SetSolid( SOLID_VPHYSICS ); \ + SetMoveType( MOVETYPE_NONE ); \ + Msg( "[StargateTC] inert stub spawned: " #classname "\n" ); \ + } \ + }; \ + LINK_ENTITY_TO_CLASS( classname, C##classname ) + +STUB_ENTITY( stargate_dialer ); +STUB_ENTITY( goauld_sarcophagus ); +STUB_ENTITY( zat_pickup ); +STUB_ENTITY( staff_pickup ); +STUB_ENTITY( naqahdah_pickup ); + +// Weapons — eventually derive from CBaseHL2MPCombatWeapon. For now an inert +// pickup so maps spawn-clean. +STUB_ENTITY( zat_gun ); +STUB_ENTITY( staff_gun ); diff --git a/game/server/stargate/dhd_console.cpp b/game/server/stargate/dhd_console.cpp new file mode 100644 index 00000000..8f43627e --- /dev/null +++ b/game/server/stargate/dhd_console.cpp @@ -0,0 +1,72 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// dhd_console — the Dial-Home-Device. A player +USEs it to fire the matching +// stargate_event's Dial input. Includes a per-DHD lockout while the gate is +// active to prevent re-dial. +// +// Keyvalues: +// target — name of the stargate_event to dial +// +//============================================================================== +#include "cbase.h" +#include "stargate/sg_constants.h" + +class CDHDConsole : public CBaseAnimating +{ +public: + DECLARE_CLASS( CDHDConsole, CBaseAnimating ); + DECLARE_DATADESC(); + + void Spawn() override; + void Precache() override; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, + USE_TYPE useType, float value ) override; + + int ObjectCaps() override { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; } + +private: + bool m_bBusy { false }; + + void Unbusy() { m_bBusy = false; SetNextThink( TICK_NEVER_THINK ); } +}; + +LINK_ENTITY_TO_CLASS( dhd_console, CDHDConsole ); + +BEGIN_DATADESC( CDHDConsole ) + DEFINE_THINKFUNC( Unbusy ), +END_DATADESC() + +void CDHDConsole::Spawn() +{ + BaseClass::Spawn(); + Precache(); + SetModel( "models/stargate/dhd_console.mdl" ); + SetSolid( SOLID_VPHYSICS ); + SetMoveType( MOVETYPE_NONE ); +} + +void CDHDConsole::Precache() +{ + PrecacheModel( "models/stargate/dhd_console.mdl" ); + PrecacheScriptSound( "Stargate.DHDPress" ); +} + +void CDHDConsole::Use( CBaseEntity *pActivator, CBaseEntity *, + USE_TYPE, float ) +{ + if ( m_bBusy || !pActivator || !pActivator->IsPlayer() ) + return; + EmitSound( "Stargate.DHDPress" ); + + CBaseEntity *pGate = gEntList.FindEntityByName( NULL, m_target ); + if ( pGate ) + { + variant_t empty; + pGate->AcceptInput( "Dial", pActivator, this, empty, 0 ); + } + m_bBusy = true; + SetThink( &CDHDConsole::Unbusy ); + SetNextThink( gpGlobals->curtime + + StargateTC::DIAL_SYMBOL_COUNT * StargateTC::DIAL_SYMBOL_TIME + + StargateTC::KAWOOSH_DURATION ); +} diff --git a/game/server/stargate/gate_travel.cpp b/game/server/stargate/gate_travel.cpp new file mode 100644 index 00000000..08dad079 --- /dev/null +++ b/game/server/stargate/gate_travel.cpp @@ -0,0 +1,64 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// zone_gatetravel and trigger_adv_teleport recovered from the GoldSrc DLL. +// Ghidra's GTravelTouch path checks IsPlayer(), clears velocity, and starts a +// short travel lockout. The Source port keeps that observable behavior and adds +// explicit destination targeting for converted maps. +// +//============================================================================= +#include "cbase.h" +#include "triggers.h" +#include "stargate/sg_constants.h" + +class CStargateTravelZone : public CBaseTrigger +{ +public: + DECLARE_CLASS( CStargateTravelZone, CBaseTrigger ); + DECLARE_DATADESC(); + + void Spawn() override; + void TravelTouch( CBaseEntity *pOther ); + +private: + void TeleportPlayer( CBaseEntity *pPlayer ); + float m_flNextTouchTime { 0.0f }; +}; + +LINK_ENTITY_TO_CLASS( zone_gatetravel, CStargateTravelZone ); +LINK_ENTITY_TO_CLASS( trigger_adv_teleport, CStargateTravelZone ); + +BEGIN_DATADESC( CStargateTravelZone ) + DEFINE_FIELD( m_flNextTouchTime, FIELD_TIME ), + DEFINE_ENTITYFUNC( TravelTouch ), +END_DATADESC() + +void CStargateTravelZone::Spawn() +{ + BaseClass::Spawn(); + InitTrigger(); + SetTouch( &CStargateTravelZone::TravelTouch ); +} + +void CStargateTravelZone::TravelTouch( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() || gpGlobals->curtime < m_flNextTouchTime ) + return; + + pOther->SetAbsVelocity( vec3_origin ); + pOther->SetBaseVelocity( vec3_origin ); + TeleportPlayer( pOther ); + + m_flNextTouchTime = gpGlobals->curtime + StargateTC::GATE_TRAVEL_COOLDOWN; +} + +void CStargateTravelZone::TeleportPlayer( CBaseEntity *pPlayer ) +{ + CBaseEntity *pDestination = gEntList.FindEntityByName( NULL, m_target ); + if ( !pDestination ) + return; + + Vector destination = pDestination->GetAbsOrigin(); + QAngle angles = pDestination->GetAbsAngles(); + Vector velocity = vec3_origin; + pPlayer->Teleport( &destination, &angles, &velocity ); +} diff --git a/game/server/stargate/multi_gate.cpp b/game/server/stargate/multi_gate.cpp new file mode 100644 index 00000000..7b44d333 --- /dev/null +++ b/game/server/stargate/multi_gate.cpp @@ -0,0 +1,213 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// Source-side port of the original GoldSrc multi_gate_1..multi_gate_49 family. +// +// Ghidra shows every multi_gate_N as a 0x248-byte CMultigateN object with the +// same ManagerUse/ManagerThink loop as CMultiManager plus a small baked target +// table. The first gate contains chevronsnd, dial1change1, dhdglyph1_on, and +// ch1relayon at zero delay; later gates follow the same naming convention. +// +//============================================================================= +#include "cbase.h" +#include "eventqueue.h" +#include "utlvector.h" + +namespace +{ + constexpr int kMaxGateTargets = 16; + + struct GateTarget + { + string_t targetName; + float delay; + }; +} + +class CStargateMultiGate : public CBaseEntity +{ +public: + DECLARE_CLASS( CStargateMultiGate, CBaseEntity ); + DECLARE_DATADESC(); + + explicit CStargateMultiGate( int gateIndex = 0 ) + : m_iGateIndex( gateIndex ), + m_iCurrentTarget( 0 ), + m_flStartTime( 0.0f ) + { + } + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void Spawn() override; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) override; + void ManagerThink(); + +protected: + void AddTarget( const char *pszTarget, float flDelay ); + void AddDefaultTargets(); + +private: + CUtlVector m_Targets; + int m_iGateIndex; + int m_iCurrentTarget; + float m_flStartTime; + EHANDLE m_hActivator; +}; + +BEGIN_DATADESC( CStargateMultiGate ) + DEFINE_FIELD( m_iGateIndex, FIELD_INTEGER ), + DEFINE_FIELD( m_iCurrentTarget, FIELD_INTEGER ), + DEFINE_FIELD( m_flStartTime, FIELD_TIME ), + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + DEFINE_THINKFUNC( ManagerThink ), +END_DATADESC() + +bool CStargateMultiGate::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( BaseClass::KeyValue( szKeyName, szValue ) ) + return true; + + if ( m_Targets.Count() < kMaxGateTargets && szValue && *szValue ) + { + char *pEnd = NULL; + const float flDelay = static_cast( strtod( szValue, &pEnd ) ); + if ( pEnd != szValue ) + { + AddTarget( szKeyName, flDelay ); + return true; + } + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CStargateMultiGate::Spawn() +{ + BaseClass::Spawn(); + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); + SetUse( &CStargateMultiGate::Use ); + + if ( m_Targets.Count() == 0 ) + AddDefaultTargets(); +} + +void CStargateMultiGate::Use( CBaseEntity *pActivator, CBaseEntity *, USE_TYPE, float ) +{ + if ( m_Targets.Count() == 0 ) + return; + + m_hActivator = pActivator; + m_iCurrentTarget = 0; + m_flStartTime = gpGlobals->curtime; + SetThink( &CStargateMultiGate::ManagerThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CStargateMultiGate::ManagerThink() +{ + const float flElapsed = gpGlobals->curtime - m_flStartTime; + + while ( m_iCurrentTarget < m_Targets.Count() && m_Targets[m_iCurrentTarget].delay <= flElapsed ) + { + const char *pszTarget = STRING( m_Targets[m_iCurrentTarget].targetName ); + variant_t emptyVariant; + g_EventQueue.AddEvent( pszTarget, "Use", emptyVariant, 0.0f, m_hActivator.Get(), this ); + ++m_iCurrentTarget; + } + + if ( m_iCurrentTarget < m_Targets.Count() ) + { + SetNextThink( m_flStartTime + m_Targets[m_iCurrentTarget].delay ); + return; + } + + SetThink( NULL ); +} + +void CStargateMultiGate::AddTarget( const char *pszTarget, float flDelay ) +{ + if ( !pszTarget || !*pszTarget || m_Targets.Count() >= kMaxGateTargets ) + return; + + GateTarget &target = m_Targets[m_Targets.AddToTail()]; + target.targetName = AllocPooledString( pszTarget ); + target.delay = flDelay; +} + +void CStargateMultiGate::AddDefaultTargets() +{ + if ( m_iGateIndex <= 0 ) + return; + + char targetName[64]; + + AddTarget( "chevronsnd", 0.0f ); + + Q_snprintf( targetName, sizeof( targetName ), "dial%dchange1", m_iGateIndex ); + AddTarget( targetName, 0.0f ); + + Q_snprintf( targetName, sizeof( targetName ), "dhdglyph%d_on", m_iGateIndex ); + AddTarget( targetName, 0.0f ); + + Q_snprintf( targetName, sizeof( targetName ), "ch%drelayon", m_iGateIndex ); + AddTarget( targetName, 0.0f ); +} + +#define LINK_MULTIGATE_ENTITY( index ) \ + class CStargateMultiGate##index : public CStargateMultiGate \ + { \ + public: \ + DECLARE_CLASS( CStargateMultiGate##index, CStargateMultiGate ); \ + CStargateMultiGate##index() : BaseClass( index ) {} \ + }; \ + LINK_ENTITY_TO_CLASS( multi_gate_##index, CStargateMultiGate##index ) + +LINK_MULTIGATE_ENTITY( 1 ); +LINK_MULTIGATE_ENTITY( 2 ); +LINK_MULTIGATE_ENTITY( 3 ); +LINK_MULTIGATE_ENTITY( 4 ); +LINK_MULTIGATE_ENTITY( 5 ); +LINK_MULTIGATE_ENTITY( 6 ); +LINK_MULTIGATE_ENTITY( 7 ); +LINK_MULTIGATE_ENTITY( 8 ); +LINK_MULTIGATE_ENTITY( 9 ); +LINK_MULTIGATE_ENTITY( 10 ); +LINK_MULTIGATE_ENTITY( 11 ); +LINK_MULTIGATE_ENTITY( 12 ); +LINK_MULTIGATE_ENTITY( 13 ); +LINK_MULTIGATE_ENTITY( 14 ); +LINK_MULTIGATE_ENTITY( 15 ); +LINK_MULTIGATE_ENTITY( 16 ); +LINK_MULTIGATE_ENTITY( 17 ); +LINK_MULTIGATE_ENTITY( 18 ); +LINK_MULTIGATE_ENTITY( 19 ); +LINK_MULTIGATE_ENTITY( 20 ); +LINK_MULTIGATE_ENTITY( 21 ); +LINK_MULTIGATE_ENTITY( 22 ); +LINK_MULTIGATE_ENTITY( 23 ); +LINK_MULTIGATE_ENTITY( 24 ); +LINK_MULTIGATE_ENTITY( 25 ); +LINK_MULTIGATE_ENTITY( 26 ); +LINK_MULTIGATE_ENTITY( 27 ); +LINK_MULTIGATE_ENTITY( 28 ); +LINK_MULTIGATE_ENTITY( 29 ); +LINK_MULTIGATE_ENTITY( 30 ); +LINK_MULTIGATE_ENTITY( 31 ); +LINK_MULTIGATE_ENTITY( 32 ); +LINK_MULTIGATE_ENTITY( 33 ); +LINK_MULTIGATE_ENTITY( 34 ); +LINK_MULTIGATE_ENTITY( 35 ); +LINK_MULTIGATE_ENTITY( 36 ); +LINK_MULTIGATE_ENTITY( 37 ); +LINK_MULTIGATE_ENTITY( 38 ); +LINK_MULTIGATE_ENTITY( 39 ); +LINK_MULTIGATE_ENTITY( 40 ); +LINK_MULTIGATE_ENTITY( 41 ); +LINK_MULTIGATE_ENTITY( 42 ); +LINK_MULTIGATE_ENTITY( 43 ); +LINK_MULTIGATE_ENTITY( 44 ); +LINK_MULTIGATE_ENTITY( 45 ); +LINK_MULTIGATE_ENTITY( 46 ); +LINK_MULTIGATE_ENTITY( 47 ); +LINK_MULTIGATE_ENTITY( 48 ); +LINK_MULTIGATE_ENTITY( 49 ); diff --git a/game/server/stargate/recovered_entities.cpp b/game/server/stargate/recovered_entities.cpp new file mode 100644 index 00000000..062b85bf --- /dev/null +++ b/game/server/stargate/recovered_entities.cpp @@ -0,0 +1,569 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// Broad registration pass for custom entities recovered from hl.dll. +// +// This file is intentionally conservative: it keeps the recovered classnames +// spawnable in Source maps without pretending the gameplay is complete. Entities +// with hand-ported behavior live in their own files and are not registered here. +// +// Source list: dll_re_workspace/output/hl/strings_categorized.json +// +//============================================================================= +#include "cbase.h" +#include "eventqueue.h" +#include "triggers.h" + +class CSGRecoveredEntity : public CBaseAnimating +{ +public: + DECLARE_CLASS( CSGRecoveredEntity, CBaseAnimating ); + DECLARE_DATADESC(); + + void Spawn() override; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) override; + +private: + COutputEvent m_OnUse; +}; + +BEGIN_DATADESC( CSGRecoveredEntity ) + DEFINE_OUTPUT( m_OnUse, "OnUse" ), +END_DATADESC() + +void CSGRecoveredEntity::Spawn() +{ + BaseClass::Spawn(); + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_NONE ); + SetUse( &CSGRecoveredEntity::Use ); + + if ( GetModelName() != NULL_STRING ) + { + PrecacheModel( STRING( GetModelName() ) ); + SetModel( STRING( GetModelName() ) ); + } +} + +void CSGRecoveredEntity::Use( CBaseEntity *pActivator, CBaseEntity *, USE_TYPE, float ) +{ + m_OnUse.FireOutput( pActivator, this ); + + if ( m_target != NULL_STRING ) + { + variant_t emptyVariant; + g_EventQueue.AddEvent( STRING( m_target ), "Use", emptyVariant, 0.0f, pActivator, this ); + } +} + +class CSGRecoveredBrushEntity : public CSGRecoveredEntity +{ +public: + DECLARE_CLASS( CSGRecoveredBrushEntity, CSGRecoveredEntity ); + + void Spawn() override + { + BaseClass::Spawn(); + if ( GetModelName() != NULL_STRING ) + SetSolid( SOLID_BSP ); + } +}; + +class CSGRecoveredClipEntity : public CSGRecoveredBrushEntity +{ +public: + DECLARE_CLASS( CSGRecoveredClipEntity, CSGRecoveredBrushEntity ); + + void Spawn() override + { + BaseClass::Spawn(); + AddEffects( EF_NODRAW ); + SetCollisionGroup( COLLISION_GROUP_PLAYER_MOVEMENT ); + } +}; + +class CSGRecoveredTriggerEntity : public CBaseTrigger +{ +public: + DECLARE_CLASS( CSGRecoveredTriggerEntity, CBaseTrigger ); + DECLARE_DATADESC(); + + void Spawn() override; + void TriggerTouch( CBaseEntity *pOther ); + +private: + COutputEvent m_OnTrigger; +}; + +BEGIN_DATADESC( CSGRecoveredTriggerEntity ) + DEFINE_OUTPUT( m_OnTrigger, "OnTrigger" ), + DEFINE_ENTITYFUNC( TriggerTouch ), +END_DATADESC() + +void CSGRecoveredTriggerEntity::Spawn() +{ + BaseClass::Spawn(); + InitTrigger(); + SetTouch( &CSGRecoveredTriggerEntity::TriggerTouch ); +} + +void CSGRecoveredTriggerEntity::TriggerTouch( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) + return; + + m_OnTrigger.FireOutput( pOther, this ); + if ( m_target != NULL_STRING ) + { + variant_t emptyVariant; + g_EventQueue.AddEvent( STRING( m_target ), "Use", emptyVariant, 0.0f, pOther, this ); + } +} + +class CSGTriggerRandom : public CSGRecoveredEntity +{ +public: + DECLARE_CLASS( CSGTriggerRandom, CSGRecoveredEntity ); + DECLARE_DATADESC(); + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void Spawn() override; + void RandomThink(); + +private: + void ScheduleNextThink(); + + float m_flMinDelay { 0.0f }; + float m_flMaxDelay { 5.0f }; +}; + +BEGIN_DATADESC( CSGTriggerRandom ) + DEFINE_FIELD( m_flMinDelay, FIELD_FLOAT ), + DEFINE_FIELD( m_flMaxDelay, FIELD_FLOAT ), + DEFINE_THINKFUNC( RandomThink ), +END_DATADESC() + +bool CSGTriggerRandom::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !Q_stricmp( szKeyName, "min" ) || !Q_stricmp( szKeyName, "mindelay" ) ) + { + m_flMinDelay = atof( szValue ); + return true; + } + + if ( !Q_stricmp( szKeyName, "max" ) || !Q_stricmp( szKeyName, "maxdelay" ) || !Q_stricmp( szKeyName, "wait" ) ) + { + m_flMaxDelay = atof( szValue ); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CSGTriggerRandom::Spawn() +{ + BaseClass::Spawn(); + SetThink( &CSGTriggerRandom::RandomThink ); + ScheduleNextThink(); +} + +void CSGTriggerRandom::RandomThink() +{ + if ( m_target != NULL_STRING ) + { + variant_t emptyVariant; + g_EventQueue.AddEvent( STRING( m_target ), "Use", emptyVariant, 0.0f, this, this ); + } + + ScheduleNextThink(); +} + +void CSGTriggerRandom::ScheduleNextThink() +{ + const float flMin = MIN( m_flMinDelay, m_flMaxDelay ); + const float flMax = MAX( m_flMinDelay, m_flMaxDelay ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( flMin, flMax ) ); +} + +class CSGTriggerGoal : public CSGRecoveredTriggerEntity +{ +public: + DECLARE_CLASS( CSGTriggerGoal, CSGRecoveredTriggerEntity ); + DECLARE_DATADESC(); + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void Spawn() override; + void GoalTouch( CBaseEntity *pOther ); + +private: + int m_iGoalTeam { 0 }; +}; + +BEGIN_DATADESC( CSGTriggerGoal ) + DEFINE_KEYFIELD( m_iGoalTeam, FIELD_INTEGER, "team" ), + DEFINE_ENTITYFUNC( GoalTouch ), +END_DATADESC() + +bool CSGTriggerGoal::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !Q_stricmp( szKeyName, "team_no" ) || !Q_stricmp( szKeyName, "goalteam" ) ) + { + m_iGoalTeam = atoi( szValue ); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CSGTriggerGoal::Spawn() +{ + BaseClass::Spawn(); + SetTouch( &CSGTriggerGoal::GoalTouch ); +} + +void CSGTriggerGoal::GoalTouch( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) + return; + + if ( m_iGoalTeam != 0 && pOther->GetTeamNumber() != m_iGoalTeam ) + return; + + TriggerTouch( pOther ); +} + +class CSGPickupEntity : public CSGRecoveredEntity +{ +public: + DECLARE_CLASS( CSGPickupEntity, CSGRecoveredEntity ); + DECLARE_DATADESC(); + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void Spawn() override; + void PickupTouch( CBaseEntity *pOther ); + void RespawnThink(); + +private: + COutputEvent m_OnPickup; + float m_flRespawnTime { -1.0f }; + bool m_bActive { true }; +}; + +BEGIN_DATADESC( CSGPickupEntity ) + DEFINE_FIELD( m_flRespawnTime, FIELD_FLOAT ), + DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), + DEFINE_OUTPUT( m_OnPickup, "OnPickup" ), + DEFINE_ENTITYFUNC( PickupTouch ), + DEFINE_THINKFUNC( RespawnThink ), +END_DATADESC() + +bool CSGPickupEntity::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !Q_stricmp( szKeyName, "respawn" ) || !Q_stricmp( szKeyName, "wait" ) ) + { + m_flRespawnTime = atof( szValue ); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CSGPickupEntity::Spawn() +{ + BaseClass::Spawn(); + SetSolid( SOLID_BBOX ); + UTIL_SetSize( this, Vector( -16, -16, 0 ), Vector( 16, 16, 32 ) ); + SetTouch( &CSGPickupEntity::PickupTouch ); +} + +void CSGPickupEntity::PickupTouch( CBaseEntity *pOther ) +{ + if ( !m_bActive || !pOther || !pOther->IsPlayer() ) + return; + + m_OnPickup.FireOutput( pOther, this ); + + if ( m_target != NULL_STRING ) + { + variant_t emptyVariant; + g_EventQueue.AddEvent( STRING( m_target ), "Use", emptyVariant, 0.0f, pOther, this ); + } + + if ( m_flRespawnTime > 0.0f ) + { + m_bActive = false; + AddEffects( EF_NODRAW ); + SetSolid( SOLID_NONE ); + SetThink( &CSGPickupEntity::RespawnThink ); + SetNextThink( gpGlobals->curtime + m_flRespawnTime ); + } + else + { + UTIL_Remove( this ); + } +} + +void CSGPickupEntity::RespawnThink() +{ + m_bActive = true; + RemoveEffects( EF_NODRAW ); + SetSolid( SOLID_BBOX ); + SetThink( NULL ); +} + +class CSGEnvToggleEntity : public CSGRecoveredEntity +{ +public: + DECLARE_CLASS( CSGEnvToggleEntity, CSGRecoveredEntity ); + DECLARE_DATADESC(); + + void InputEnable( inputdata_t & ) { RemoveEffects( EF_NODRAW ); } + void InputDisable( inputdata_t & ) { AddEffects( EF_NODRAW ); } + void InputToggle( inputdata_t & ) + { + if ( IsEffectActive( EF_NODRAW ) ) + RemoveEffects( EF_NODRAW ); + else + AddEffects( EF_NODRAW ); + } +}; + +BEGIN_DATADESC( CSGEnvToggleEntity ) + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), +END_DATADESC() + +class CSGTargetCDAudio : public CSGRecoveredEntity +{ +public: + DECLARE_CLASS( CSGTargetCDAudio, CSGRecoveredEntity ); + DECLARE_DATADESC(); + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void InputPlay( inputdata_t & ); + void InputStop( inputdata_t & ); + +private: + int m_iTrack { -1 }; + COutputInt m_OnTrackChanged; +}; + +BEGIN_DATADESC( CSGTargetCDAudio ) + DEFINE_FIELD( m_iTrack, FIELD_INTEGER ), + DEFINE_OUTPUT( m_OnTrackChanged, "OnTrackChanged" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Play", InputPlay ), + DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ), +END_DATADESC() + +bool CSGTargetCDAudio::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !Q_stricmp( szKeyName, "track" ) ) + { + m_iTrack = atoi( szValue ); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CSGTargetCDAudio::InputPlay( inputdata_t &inputdata ) +{ + m_OnTrackChanged.Set( m_iTrack, inputdata.pActivator ? inputdata.pActivator : this, this ); +} + +void CSGTargetCDAudio::InputStop( inputdata_t &inputdata ) +{ + m_OnTrackChanged.Set( -1, inputdata.pActivator ? inputdata.pActivator : this, this ); +} + +class CSGMonsterProxy : public CBaseAnimating +{ +public: + DECLARE_CLASS( CSGMonsterProxy, CBaseAnimating ); + DECLARE_DATADESC(); + + bool KeyValue( const char *szKeyName, const char *szValue ) override; + void Spawn() override; + int OnTakeDamage( const CTakeDamageInfo &info ) override; + void ProxyTouch( CBaseEntity *pOther ); + +private: + int m_iStartHealth { 60 }; + int m_iTouchDamage { 0 }; + COutputEvent m_OnKilled; +}; + +BEGIN_DATADESC( CSGMonsterProxy ) + DEFINE_KEYFIELD( m_iStartHealth, FIELD_INTEGER, "health" ), + DEFINE_KEYFIELD( m_iTouchDamage, FIELD_INTEGER, "touchdamage" ), + DEFINE_OUTPUT( m_OnKilled, "OnKilled" ), + DEFINE_ENTITYFUNC( ProxyTouch ), +END_DATADESC() + +bool CSGMonsterProxy::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( !Q_stricmp( szKeyName, "dmg" ) ) + { + m_iTouchDamage = atoi( szValue ); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +void CSGMonsterProxy::Spawn() +{ + BaseClass::Spawn(); + SetMoveType( MOVETYPE_STEP ); + SetSolid( SOLID_BBOX ); + UTIL_SetSize( this, Vector( -16, -16, 0 ), Vector( 16, 16, 72 ) ); + m_takedamage = DAMAGE_YES; + m_iHealth = m_iStartHealth; + SetTouch( &CSGMonsterProxy::ProxyTouch ); + + if ( GetModelName() != NULL_STRING ) + { + PrecacheModel( STRING( GetModelName() ) ); + SetModel( STRING( GetModelName() ) ); + } +} + +int CSGMonsterProxy::OnTakeDamage( const CTakeDamageInfo &info ) +{ + const int result = BaseClass::OnTakeDamage( info ); + if ( m_iHealth <= 0 ) + { + m_OnKilled.FireOutput( info.GetAttacker() ? info.GetAttacker() : this, this ); + UTIL_Remove( this ); + } + return result; +} + +void CSGMonsterProxy::ProxyTouch( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() || m_iTouchDamage <= 0 ) + return; + + CTakeDamageInfo dmgInfo( this, this, float( m_iTouchDamage ), DMG_SLASH ); + pOther->TakeDamage( dmgInfo ); +} + +class CSGTriggerSecret : public CSGRecoveredTriggerEntity +{ +public: + DECLARE_CLASS( CSGTriggerSecret, CSGRecoveredTriggerEntity ); + DECLARE_DATADESC(); + + void SecretTouch( CBaseEntity *pOther ); + +private: + static int s_iSecretsFound; + COutputInt m_OnSecretFound; + bool m_bTriggered { false }; +}; + +int CSGTriggerSecret::s_iSecretsFound = 0; + +BEGIN_DATADESC( CSGTriggerSecret ) + DEFINE_FIELD( m_bTriggered, FIELD_BOOLEAN ), + DEFINE_OUTPUT( m_OnSecretFound, "OnSecretFound" ), + DEFINE_ENTITYFUNC( SecretTouch ), +END_DATADESC() + +void CSGTriggerSecret::SecretTouch( CBaseEntity *pOther ) +{ + if ( m_bTriggered || !pOther || !pOther->IsPlayer() ) + return; + + m_bTriggered = true; + ++s_iSecretsFound; + m_OnSecretFound.Set( s_iSecretsFound, pOther, this ); + TriggerTouch( pOther ); + SetTouch( NULL ); +} + +// Ammo and items. +LINK_ENTITY_TO_CLASS( ammo_9mmAR, CSGPickupEntity ); +LINK_ENTITY_TO_CLASS( ammo_psg1, CSGPickupEntity ); +LINK_ENTITY_TO_CLASS( item_NVG, CSGPickupEntity ); +LINK_ENTITY_TO_CLASS( item_airtank, CSGPickupEntity ); +LINK_ENTITY_TO_CLASS( item_objective, CSGPickupEntity ); + +// Cyclers. (cycler/cycler_weapon/cycler_wreckage are registered in h_cycler.cpp — omitted to avoid double-registration heap corruption) +LINK_ENTITY_TO_CLASS( cycler_prdroid, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( cycler_sprite, CSGRecoveredEntity ); + +// Environment helpers. +LINK_ENTITY_TO_CLASS( env_debris, CSGEnvToggleEntity ); +LINK_ENTITY_TO_CLASS( env_fog, CSGEnvToggleEntity ); +LINK_ENTITY_TO_CLASS( env_fountain, CSGEnvToggleEntity ); +LINK_ENTITY_TO_CLASS( env_lensflare, CSGEnvToggleEntity ); +LINK_ENTITY_TO_CLASS( env_model, CSGEnvToggleEntity ); +LINK_ENTITY_TO_CLASS( env_particule, CSGEnvToggleEntity ); + +// Brush and movement helpers. +// (func_guntarget/func_wall_toggle/func_traincontrols/func_trackchange/func_trackautochange +// are all registered in SDK base — omitted to avoid double-registration heap corruption) +LINK_ENTITY_TO_CLASS( func_mortar_field, CSGRecoveredBrushEntity ); +LINK_ENTITY_TO_CLASS( func_tankgoauld, CSGRecoveredBrushEntity ); +LINK_ENTITY_TO_CLASS( func_monsterclip, CSGRecoveredClipEntity ); + +// Game event hooks. +LINK_ENTITY_TO_CLASS( game_nuke, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( game_playerdie, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( game_playerjoin, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( game_playerkill, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( game_playerleave, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( game_playerspawn, CSGRecoveredEntity ); + +// Info and marker entities. info_player_* team spawns have a more +// specific spawnpoint implementation in sg_player_spawn.cpp. +LINK_ENTITY_TO_CLASS( info_cap_rules, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( info_flash, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( info_marker, CSGRecoveredEntity ); +// info_null is registered in SDK base (subs.cpp) — omitted to avoid double-registration +LINK_ENTITY_TO_CLASS( info_observer, CSGRecoveredEntity ); +LINK_ENTITY_TO_CLASS( info_radar_block, CSGRecoveredEntity ); +// target_cdaudio is registered in SDK base (controlentities.cpp) — omitted to avoid double-registration + +// Monsters. AI/abilities remain to be ported, but these now have damage and touch behavior. +LINK_ENTITY_TO_CLASS( monster_barney_dead, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_bloater, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine2_hvyweapons, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine2_scientist, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine2_slave, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine3_barney, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine3_scientist, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine_barney, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine_panther, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cine_scientist, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_cockroach, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_flyer, CSGMonsterProxy ); +// monster_furniture is registered in SDK base (genericmonster.cpp) — omitted to avoid double-registration +LINK_ENTITY_TO_CLASS( monster_grunt_repel, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_implanted, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_larve, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_mine, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_mortar, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_nihilanth, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_replic, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_replicateur, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_replicator, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_satchel, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_scientist_dead, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_skull, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_tac, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_tentaclemaw, CSGMonsterProxy ); +LINK_ENTITY_TO_CLASS( monster_vortigaunt, CSGMonsterProxy ); + +// Additional triggers. trigger_adv_teleport is hand-ported in gate_travel.cpp. +LINK_ENTITY_TO_CLASS( trigger_goal, CSGTriggerGoal ); +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CSGRecoveredTriggerEntity ); +LINK_ENTITY_TO_CLASS( trigger_random, CSGTriggerRandom ); +LINK_ENTITY_TO_CLASS( trigger_secret, CSGTriggerSecret ); +// trigger_transition is registered in SDK base (triggers.cpp) — omitted to avoid double-registration + +// Weapons are registered as real predicted client/server HL2MP weapons in +// sourcecode/shared/stargate/sg_weapons.cpp. diff --git a/game/server/stargate/ring_transporter.cpp b/game/server/stargate/ring_transporter.cpp new file mode 100644 index 00000000..f5c1bb73 --- /dev/null +++ b/game/server/stargate/ring_transporter.cpp @@ -0,0 +1,69 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// ring_transporter — Goa'uld ring teleport pad. A player stepping onto the pad +// gets sent to the paired pad after a short delay. Pairs are matched via the +// `target` keyvalue (each ring names the other). +// +//============================================================================== +#include "cbase.h" +#include "stargate/sg_constants.h" + +class CRingTransporter : public CBaseEntity +{ +public: + DECLARE_CLASS( CRingTransporter, CBaseEntity ); + DECLARE_DATADESC(); + + void Spawn() override; + void TouchTransport( CBaseEntity *pOther ); + void DoTransport(); + void Cooldown() { m_flNextActive = 0; SetNextThink( TICK_NEVER_THINK ); } + +private: + EHANDLE m_hPending; + float m_flNextActive { 0.0f }; +}; + +LINK_ENTITY_TO_CLASS( ring_transporter, CRingTransporter ); + +BEGIN_DATADESC( CRingTransporter ) + DEFINE_FIELD( m_hPending, FIELD_EHANDLE ), + DEFINE_FIELD( m_flNextActive, FIELD_TIME ), + DEFINE_ENTITYFUNC( TouchTransport ), + DEFINE_THINKFUNC( DoTransport ), +END_DATADESC() + + +void CRingTransporter::Spawn() +{ + BaseClass::Spawn(); + SetSolid( SOLID_BBOX ); + SetCollisionGroup( COLLISION_GROUP_NONE ); + UTIL_SetSize( this, Vector(-48,-48,0), Vector(48,48,16) ); + SetTouch( &CRingTransporter::TouchTransport ); +} + +void CRingTransporter::TouchTransport( CBaseEntity *pOther ) +{ + if ( !pOther || !pOther->IsPlayer() ) return; + if ( gpGlobals->curtime < m_flNextActive ) return; + + m_hPending = pOther; + m_flNextActive = gpGlobals->curtime + StargateTC::RING_COOLDOWN; + SetThink( &CRingTransporter::DoTransport ); + SetNextThink( gpGlobals->curtime + StargateTC::RING_TRAVEL_TIME ); + // TODO: spawn ring VFX + sound here. +} + +void CRingTransporter::DoTransport() +{ + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target ); + CBaseEntity *pPlayer = m_hPending; + if ( pTarget && pPlayer ) + { + pPlayer->Teleport( &pTarget->GetAbsOrigin(), NULL, NULL ); + } + m_hPending = NULL; + SetThink( &CRingTransporter::Cooldown ); + SetNextThink( gpGlobals->curtime + StargateTC::RING_COOLDOWN ); +} diff --git a/game/server/stargate/sg_event.cpp b/game/server/stargate/sg_event.cpp new file mode 100644 index 00000000..ea0887b6 --- /dev/null +++ b/game/server/stargate/sg_event.cpp @@ -0,0 +1,126 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// stargate_event — the master entity that drives a Stargate dial. Other gate +// pieces (the rotating ring brushwork, chevron light props, kawoosh emitter) +// hook to this via parentname / OnDial outputs. +// +// Inputs: +// Dial — kick off the 7-chevron sequence +// ForceClose — interrupt the dial (or shut down an open wormhole) +// Lock — disable for this round +// +// Outputs: +// OnDialStart +// OnChevronLock (passes chevron index 1..7) +// OnKawoosh +// OnWormholeOpen +// OnWormholeClose +// +// First-pass behavior: spawns, registers, accepts inputs, fires outputs on a +// fixed schedule driven by StargateTC::DIAL_SYMBOL_TIME. No gameplay +// consequences yet — those land when the player-teleport + kawoosh damage +// logic ports. +// +//============================================================================== +#include "cbase.h" +#include "stargate/sg_constants.h" + +class CStargateEvent : public CBaseEntity +{ +public: + DECLARE_CLASS( CStargateEvent, CBaseEntity ); + DECLARE_DATADESC(); + + void Spawn() override; + void Precache() override; + + // I/O + void InputDial( inputdata_t &inputdata ); + void InputForceClose( inputdata_t &inputdata ); + void InputLock( inputdata_t &inputdata ); + + void DialThink(); + void WormholeOpenThink(); + +private: + int m_iChevronsLocked { 0 }; + bool m_bLocked { false }; + + COutputEvent m_OnDialStart; + COutputInt m_OnChevronLock; + COutputEvent m_OnKawoosh; + COutputEvent m_OnWormholeOpen; + COutputEvent m_OnWormholeClose; +}; + +LINK_ENTITY_TO_CLASS( stargate_event, CStargateEvent ); + +BEGIN_DATADESC( CStargateEvent ) + DEFINE_INPUTFUNC( FIELD_VOID, "Dial", InputDial ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceClose", InputForceClose ), + DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), + DEFINE_OUTPUT( m_OnDialStart, "OnDialStart" ), + DEFINE_OUTPUT( m_OnChevronLock, "OnChevronLock" ), + DEFINE_OUTPUT( m_OnKawoosh, "OnKawoosh" ), + DEFINE_OUTPUT( m_OnWormholeOpen, "OnWormholeOpen" ), + DEFINE_OUTPUT( m_OnWormholeClose, "OnWormholeClose" ), + DEFINE_THINKFUNC( DialThink ), + DEFINE_THINKFUNC( WormholeOpenThink ), +END_DATADESC() + + +void CStargateEvent::Spawn() +{ + BaseClass::Spawn(); + Precache(); + SetSolid( SOLID_NONE ); +} + +void CStargateEvent::Precache() +{ + // TODO: precache gate ring rotation sound, chevron lock click, kawoosh whoosh. +} + +void CStargateEvent::InputDial( inputdata_t & ) +{ + if ( m_bLocked ) + return; + m_iChevronsLocked = 0; + m_OnDialStart.FireOutput( this, this ); + SetThink( &CStargateEvent::DialThink ); + SetNextThink( gpGlobals->curtime + StargateTC::DIAL_SYMBOL_TIME ); +} + +void CStargateEvent::InputForceClose( inputdata_t & ) +{ + SetThink( NULL ); + m_iChevronsLocked = 0; + m_OnWormholeClose.FireOutput( this, this ); +} + +void CStargateEvent::InputLock( inputdata_t & ) +{ + m_bLocked = true; + SetThink( NULL ); +} + +void CStargateEvent::DialThink() +{ + m_iChevronsLocked++; + m_OnChevronLock.Set( m_iChevronsLocked, this, this ); + + if ( m_iChevronsLocked >= StargateTC::DIAL_SYMBOL_COUNT ) + { + m_OnKawoosh.FireOutput( this, this ); + SetThink( &CStargateEvent::WormholeOpenThink ); + SetNextThink( gpGlobals->curtime + StargateTC::KAWOOSH_DURATION ); + return; + } + SetNextThink( gpGlobals->curtime + StargateTC::DIAL_SYMBOL_TIME ); +} + +void CStargateEvent::WormholeOpenThink() +{ + m_OnWormholeOpen.FireOutput( this, this ); + SetThink( NULL ); +} diff --git a/game/server/stargate/sg_kawoosh.cpp b/game/server/stargate/sg_kawoosh.cpp new file mode 100644 index 00000000..48cded05 --- /dev/null +++ b/game/server/stargate/sg_kawoosh.cpp @@ -0,0 +1,54 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// stargate_kawoosh - unstable vortex damage volume. The original GoldSrc DLL +// exposes this as a custom gate entity; this Source pass makes it operational +// through normal inputs so stargate_event can trigger it from Hammer I/O. +// +//============================================================================= +#include "cbase.h" +#include "stargate/sg_constants.h" + +class CStargateKawoosh : public CBaseEntity +{ +public: + DECLARE_CLASS( CStargateKawoosh, CBaseEntity ); + DECLARE_DATADESC(); + + void Spawn() override; + void InputFire( inputdata_t &inputdata ); + void CollapseThink(); + +private: + float m_flRadius; +}; + +LINK_ENTITY_TO_CLASS( stargate_kawoosh, CStargateKawoosh ); + +BEGIN_DATADESC( CStargateKawoosh ) + DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Fire", InputFire ), + DEFINE_THINKFUNC( CollapseThink ), +END_DATADESC() + +void CStargateKawoosh::Spawn() +{ + BaseClass::Spawn(); + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); + + if ( m_flRadius <= 0.0f ) + m_flRadius = StargateTC::KAWOOSH_RADIUS; +} + +void CStargateKawoosh::InputFire( inputdata_t & ) +{ + CTakeDamageInfo info( this, this, StargateTC::KAWOOSH_DAMAGE, DMG_ENERGYBEAM | DMG_DISSOLVE ); + RadiusDamage( info, GetAbsOrigin(), m_flRadius, CLASS_NONE, NULL ); + SetThink( &CStargateKawoosh::CollapseThink ); + SetNextThink( gpGlobals->curtime + StargateTC::KAWOOSH_DURATION ); +} + +void CStargateKawoosh::CollapseThink() +{ + SetThink( NULL ); +} diff --git a/game/server/stargate/sg_player_spawn.cpp b/game/server/stargate/sg_player_spawn.cpp new file mode 100644 index 00000000..318e6b2e --- /dev/null +++ b/game/server/stargate/sg_player_spawn.cpp @@ -0,0 +1,63 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// info_player_torri / info_player_goauld — team-flavored multiplayer spawn +// markers. The entity translator (pipeline/stargate_pipeline/entities/translator.py) +// rewrites these to info_player_deathmatch + a "team" keyvalue at port time, +// so HL2DM's spawn picker handles them automatically. We still register the +// raw classnames here in case a hand-authored Source-side map uses them. +// +//============================================================================== +#include "cbase.h" +#include "stargate/sg_constants.h" + +class CSGSpawnPoint : public CPointEntity +{ +public: + DECLARE_CLASS( CSGSpawnPoint, CPointEntity ); + void Spawn() override + { + BaseClass::Spawn(); + SetSolid( SOLID_NONE ); + } + virtual int PreferredTeam() = 0; +}; + +class CSGSpawnPointTorri : public CSGSpawnPoint +{ +public: + DECLARE_CLASS( CSGSpawnPointTorri, CSGSpawnPoint ); + int PreferredTeam() override { return StargateTC::TEAM_TORRI; } +}; +LINK_ENTITY_TO_CLASS( info_player_torri, CSGSpawnPointTorri ); + +class CSGSpawnPointGoauld : public CSGSpawnPoint +{ +public: + DECLARE_CLASS( CSGSpawnPointGoauld, CSGSpawnPoint ); + int PreferredTeam() override { return StargateTC::TEAM_GOAULD; } +}; +LINK_ENTITY_TO_CLASS( info_player_goauld, CSGSpawnPointGoauld ); + +class CSGSpawnPointMarine : public CSGSpawnPoint +{ +public: + DECLARE_CLASS( CSGSpawnPointMarine, CSGSpawnPoint ); + int PreferredTeam() override { return StargateTC::TEAM_TORRI; } +}; +LINK_ENTITY_TO_CLASS( info_player_marine, CSGSpawnPointMarine ); + +class CSGSpawnPointUnas : public CSGSpawnPoint +{ +public: + DECLARE_CLASS( CSGSpawnPointUnas, CSGSpawnPoint ); + int PreferredTeam() override { return StargateTC::TEAM_UNAS; } +}; +LINK_ENTITY_TO_CLASS( info_player_unas, CSGSpawnPointUnas ); + +class CSGSpawnPointCoop : public CSGSpawnPoint +{ +public: + DECLARE_CLASS( CSGSpawnPointCoop, CSGSpawnPoint ); + int PreferredTeam() override { return StargateTC::TEAM_NONE; } +}; +LINK_ENTITY_TO_CLASS( info_player_coop, CSGSpawnPointCoop ); diff --git a/game/shared/hl2mp/hl2mp_gamerules.cpp b/game/shared/hl2mp/hl2mp_gamerules.cpp index b59ddf2e..8d29d949 100644 --- a/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -115,6 +115,11 @@ static const char *s_PreserveEnts[] = "info_player_deathmatch", "info_player_combine", "info_player_rebel", + "info_player_torri", + "info_player_marine", + "info_player_goauld", + "info_player_unas", + "info_player_coop", "info_map_parameters", "keyframe_rope", "move_rope", @@ -179,8 +184,10 @@ char *sTeamNames[] = { "Unassigned", "Spectator", - "Combine", - "Rebels", + "Tau'ri", + "Goa'uld", + "Jaffa", + "Unas", }; CHL2MPRules::CHL2MPRules() @@ -844,10 +851,10 @@ int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget const char *CHL2MPRules::GetGameDescription( void ) { if ( IsTeamplay() ) - return "Team Deathmatch"; + return "StargateTC Teamplay"; - return "Deathmatch"; -} + return "StargateTC"; +} bool CHL2MPRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ) { diff --git a/game/shared/hl2mp/hl2mp_gamerules.h b/game/shared/hl2mp/hl2mp_gamerules.h index 8d91554c..f050a6f5 100644 --- a/game/shared/hl2mp/hl2mp_gamerules.h +++ b/game/shared/hl2mp/hl2mp_gamerules.h @@ -28,8 +28,10 @@ enum { - TEAM_COMBINE = 2, - TEAM_REBELS, + TEAM_COMBINE = 2, // StargateTC: Tau'ri / SG team slot + TEAM_REBELS, // StargateTC: Goa'uld slot + TEAM_JAFFA, + TEAM_UNAS, }; diff --git a/game/shared/stargate/sg_constants.h b/game/shared/stargate/sg_constants.h new file mode 100644 index 00000000..92b98eae --- /dev/null +++ b/game/shared/stargate/sg_constants.h @@ -0,0 +1,55 @@ +//============= Copyright © StargateTC Source Team ============================= +// +// Shared constants for the Stargate gameplay layer. Mirror these in any client- +// side prediction code. Numbers come from the original GoldSrc mod's +// `dlls/sg_*.cpp` files and from gameplay testing notes in +// `stargatetc-portable-main/HPB_bot_ReadMe.txt`. +// +//============================================================================== +#ifndef SG_CONSTANTS_H +#define SG_CONSTANTS_H +#ifdef _WIN32 +#pragma once +#endif + +namespace StargateTC +{ + // Weapons + constexpr float ZAT_DAMAGE_FIRST_SHOT = 35.0f; // stuns + constexpr float ZAT_DAMAGE_SECOND_SHOT = 999.0f; // kills + constexpr float ZAT_REFIRE_TIME = 0.9f; + constexpr float ZAT_RANGE = 2048.0f; + + constexpr float STAFF_DAMAGE_DIRECT = 60.0f; + constexpr float STAFF_DAMAGE_RADIUS = 30.0f; + constexpr float STAFF_RADIUS = 96.0f; + constexpr float STAFF_REFIRE_TIME = 1.2f; + + // Dial / event + constexpr float DIAL_SYMBOL_TIME = 1.6f; // per chevron lock + constexpr int DIAL_SYMBOL_COUNT = 7; + constexpr float KAWOOSH_DURATION = 1.8f; + constexpr float KAWOOSH_DAMAGE = 9999.0f;// instakill anything in volume + constexpr float KAWOOSH_RADIUS = 256.0f; + constexpr float GATE_TRAVEL_COOLDOWN = 1.0f; + + // Ring transporter + constexpr float RING_TRAVEL_TIME = 1.5f; + constexpr float RING_COOLDOWN = 4.0f; + + // Sarcophagus + constexpr float SARC_REGEN_PER_SEC = 10.0f; + constexpr float SARC_MAX_HEAL = 100.0f; + + // Teams (carried through info_player_torri / info_player_goauld) + enum Team : int + { + TEAM_NONE = 0, + TEAM_TORRI = 2, // Tau'ri / SG-1 + TEAM_GOAULD = 3, + TEAM_JAFFA = 4, + TEAM_UNAS = 5, + }; +} + +#endif // SG_CONSTANTS_H diff --git a/game/shared/stargate/sg_weapons.cpp b/game/shared/stargate/sg_weapons.cpp new file mode 100644 index 00000000..3b3e1822 --- /dev/null +++ b/game/shared/stargate/sg_weapons.cpp @@ -0,0 +1,296 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// Broad Source-side weapon port for StargateTC's recovered GoldSrc weapon +// classnames. The decompile provides the original entity names and model assets +// (`weapon_*`, `models/v_*`, `models/p_*`, `models/w_*`) plus method families +// for melee weapons, grenades, projectiles, and visibility/support devices. +// +// This file keeps every recovered weapon as a real predicted HL2MP weapon on +// client and server. Weapon-specific script files provide damage, ammo, clips, +// HUD buckets, and model paths. +// +//============================================================================= +#include "cbase.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "in_buttons.h" +#include "npcevent.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" + #include "grenade_frag.h" +#endif + +#include "tier0/memdbgon.h" + +#ifdef CLIENT_DLL +#define CSTCWeapon9mmAR C_STCWeapon9mmAR +#define CSTCWeaponBatonSupplice C_STCWeaponBatonSupplice +#define CSTCWeaponC4 C_STCWeaponC4 +#define CSTCWeaponCoujaf C_STCWeaponCoujaf +#define CSTCWeaponCouteau C_STCWeaponCouteau +#define CSTCWeaponFlashbang C_STCWeaponFlashbang +#define CSTCWeaponGacGoa C_STCWeaponGacGoa +#define CSTCWeaponGlStaff C_STCWeaponGlStaff +#define CSTCWeaponGoaRegen C_STCWeaponGoaRegen +#define CSTCWeaponInvis C_STCWeaponInvis +#define CSTCWeaponLance C_STCWeaponLance +#define CSTCWeaponLarve C_STCWeaponLarve +#define CSTCWeaponM16 C_STCWeaponM16 +#define CSTCWeaponM92S C_STCWeaponM92S +#define CSTCWeaponMain C_STCWeaponMain +#define CSTCWeaponMedic C_STCWeaponMedic +#define CSTCWeaponMine C_STCWeaponMine +#define CSTCWeaponMustardGrenade C_STCWeaponMustardGrenade +#define CSTCWeaponNarcoGrenade C_STCWeaponNarcoGrenade +#define CSTCWeaponNerveGrenade C_STCWeaponNerveGrenade +#define CSTCWeaponP90 C_STCWeaponP90 +#define CSTCWeaponPSG1 C_STCWeaponPSG1 +#define CSTCWeaponReetou C_STCWeaponReetou +#define CSTCWeaponSmokeGrenade C_STCWeaponSmokeGrenade +#define CSTCWeaponSPAS C_STCWeaponSPAS +#define CSTCWeaponTacGun C_STCWeaponTacGun +#define CSTCWeaponUSAS C_STCWeaponUSAS +#define CSTCWeaponZat C_STCWeaponZat +#define CSTCWeaponZatArc C_STCWeaponZatArc +#endif + +enum STCWeaponKind +{ + STC_WEAPON_HITSCAN, + STC_WEAPON_MELEE, + STC_WEAPON_GRENADE, + STC_WEAPON_SUPPORT, +}; + +struct STCWeaponConfig +{ + STCWeaponKind kind; + float fireRate; + float meleeRange; + float meleeDamage; + Vector spread; +}; + +static const STCWeaponConfig kSTCWeaponRifle = { STC_WEAPON_HITSCAN, 0.10f, 0.0f, 0.0f, VECTOR_CONE_4DEGREES }; +static const STCWeaponConfig kSTCWeaponPistol = { STC_WEAPON_HITSCAN, 0.22f, 0.0f, 0.0f, VECTOR_CONE_3DEGREES }; +static const STCWeaponConfig kSTCWeaponShotgun = { STC_WEAPON_HITSCAN, 0.80f, 0.0f, 0.0f, VECTOR_CONE_10DEGREES }; +static const STCWeaponConfig kSTCWeaponSniper = { STC_WEAPON_HITSCAN, 1.20f, 0.0f, 0.0f, VECTOR_CONE_1DEGREES }; +static const STCWeaponConfig kSTCWeaponEnergy = { STC_WEAPON_HITSCAN, 0.75f, 0.0f, 0.0f, VECTOR_CONE_2DEGREES }; +static const STCWeaponConfig kSTCWeaponMelee = { STC_WEAPON_MELEE, 0.45f, 72.0f, 35.0f, vec3_origin }; +static const STCWeaponConfig kSTCWeaponHeavyMelee = { STC_WEAPON_MELEE, 0.65f, 88.0f, 55.0f, vec3_origin }; +static const STCWeaponConfig kSTCWeaponGrenade = { STC_WEAPON_GRENADE, 1.00f, 0.0f, 0.0f, vec3_origin }; +static const STCWeaponConfig kSTCWeaponSupport = { STC_WEAPON_SUPPORT, 0.80f, 0.0f, 0.0f, vec3_origin }; + +class CSTCWeaponBase : public CBaseHL2MPCombatWeapon +{ +public: + DECLARE_CLASS( CSTCWeaponBase, CBaseHL2MPCombatWeapon ); + + CSTCWeaponBase() + { + m_bFiresUnderwater = true; + } + + virtual const STCWeaponConfig &GetSTCConfig() const = 0; + + void Precache() override + { + BaseClass::Precache(); +#ifndef CLIENT_DLL + if ( GetSTCConfig().kind == STC_WEAPON_GRENADE ) + UTIL_PrecacheOther( "npc_grenade_frag" ); +#endif + } + + void PrimaryAttack() override + { + const STCWeaponConfig &config = GetSTCConfig(); + + if ( config.kind == STC_WEAPON_MELEE ) + { + MeleeAttack(); + return; + } + + if ( config.kind == STC_WEAPON_GRENADE ) + { + ThrowGrenade(); + return; + } + + if ( config.kind == STC_WEAPON_SUPPORT ) + { + SupportUse(); + return; + } + + BaseClass::PrimaryAttack(); + } + + float GetFireRate() override + { + return GetSTCConfig().fireRate; + } + + const Vector &GetBulletSpread() override + { + return GetSTCConfig().spread; + } + + Activity GetPrimaryAttackActivity() override + { + return ACT_VM_PRIMARYATTACK; + } + +private: + void MeleeAttack() + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !pOwner ) + return; + + WeaponSound( MELEE_MISS ); + SendWeaponAnim( ACT_VM_HITCENTER ); + pOwner->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + Vector vecForward; + AngleVectors( pOwner->EyeAngles(), &vecForward ); + + const Vector vecSrc = pOwner->Weapon_ShootPosition(); + const Vector vecEnd = vecSrc + vecForward * GetSTCConfig().meleeRange; + + trace_t tr; + UTIL_TraceLine( vecSrc, vecEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction < 1.0f && tr.m_pEnt ) + { + CTakeDamageInfo info( pOwner, pOwner, GetSTCConfig().meleeDamage, DMG_CLUB ); + tr.m_pEnt->TakeDamage( info ); + WeaponSound( MELEE_HIT ); + } +#endif + + m_flNextPrimaryAttack = gpGlobals->curtime + GetSTCConfig().fireRate; + m_flNextSecondaryAttack = m_flNextPrimaryAttack; + } + + void ThrowGrenade() + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !pOwner ) + return; + + if ( UsesClipsForAmmo1() && m_iClip1 <= 0 ) + { + Reload(); + return; + } + + if ( !UsesClipsForAmmo1() && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + return; + + WeaponSound( SINGLE ); + SendWeaponAnim( ACT_VM_THROW ); + pOwner->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + Vector vecForward; + AngleVectors( pOwner->EyeAngles(), &vecForward ); + + const Vector vecSrc = pOwner->Weapon_ShootPosition() + vecForward * 16.0f; + const Vector vecThrow = vecForward * 900.0f + pOwner->GetAbsVelocity(); + Fraggrenade_Create( + vecSrc, + vec3_angle, + vecThrow, + AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ), + pOwner, + 3.0f, + false ); +#endif + + if ( UsesClipsForAmmo1() ) + --m_iClip1; + else + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); + + m_flNextPrimaryAttack = gpGlobals->curtime + GetSTCConfig().fireRate; + m_flNextSecondaryAttack = m_flNextPrimaryAttack; + } + + void SupportUse() + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !pOwner ) + return; + + WeaponSound( SINGLE ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + pOwner->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + if ( !Q_stricmp( GetClassname(), "weapon_medic" ) || !Q_stricmp( GetClassname(), "weapon_goaregen" ) ) + { + pOwner->TakeHealth( 20.0f, DMG_GENERIC ); + } + else if ( !Q_stricmp( GetClassname(), "weapon_invis" ) || !Q_stricmp( GetClassname(), "weapon_reetou" ) ) + { + pOwner->AddEffects( EF_NODRAW ); + pOwner->SetRenderMode( kRenderTransTexture ); + pOwner->SetRenderColorA( 80 ); + } +#endif + + m_flNextPrimaryAttack = gpGlobals->curtime + GetSTCConfig().fireRate; + m_flNextSecondaryAttack = m_flNextPrimaryAttack; + } +}; + +#define DECLARE_STC_WEAPON( className, networkName, entityName, configName ) \ + class className : public CSTCWeaponBase \ + { \ + public: \ + DECLARE_CLASS( className, CSTCWeaponBase ); \ + DECLARE_NETWORKCLASS(); \ + DECLARE_PREDICTABLE(); \ + const STCWeaponConfig &GetSTCConfig() const override { return configName; } \ + }; \ + IMPLEMENT_NETWORKCLASS_ALIASED( networkName, DT_##networkName ) \ + BEGIN_NETWORK_TABLE( className, DT_##networkName ) \ + END_NETWORK_TABLE() \ + BEGIN_PREDICTION_DATA( className ) \ + END_PREDICTION_DATA() \ + LINK_ENTITY_TO_CLASS( entityName, className ); \ + PRECACHE_WEAPON_REGISTER( entityName ) + +DECLARE_STC_WEAPON( CSTCWeapon9mmAR, STCWeapon9mmAR, weapon_9mmAR, kSTCWeaponRifle ); +DECLARE_STC_WEAPON( CSTCWeaponBatonSupplice, STCWeaponBatonSupplice, weapon_batsup, kSTCWeaponHeavyMelee ); +DECLARE_STC_WEAPON( CSTCWeaponC4, STCWeaponC4, weapon_c4, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponCoujaf, STCWeaponCoujaf, weapon_coujaf, kSTCWeaponHeavyMelee ); +DECLARE_STC_WEAPON( CSTCWeaponCouteau, STCWeaponCouteau, weapon_couteau, kSTCWeaponMelee ); +DECLARE_STC_WEAPON( CSTCWeaponFlashbang, STCWeaponFlashbang, weapon_flashbang, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponGacGoa, STCWeaponGacGoa, weapon_gacgoa, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponGlStaff, STCWeaponGlStaff, weapon_glstaff, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponGoaRegen, STCWeaponGoaRegen, weapon_goaregen, kSTCWeaponSupport ); +DECLARE_STC_WEAPON( CSTCWeaponInvis, STCWeaponInvis, weapon_invis, kSTCWeaponSupport ); +DECLARE_STC_WEAPON( CSTCWeaponLance, STCWeaponLance, weapon_lance, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponLarve, STCWeaponLarve, weapon_larve, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponM16, STCWeaponM16, weapon_m16, kSTCWeaponRifle ); +DECLARE_STC_WEAPON( CSTCWeaponM92S, STCWeaponM92S, weapon_m92s, kSTCWeaponPistol ); +DECLARE_STC_WEAPON( CSTCWeaponMain, STCWeaponMain, weapon_main, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponMedic, STCWeaponMedic, weapon_medic, kSTCWeaponSupport ); +DECLARE_STC_WEAPON( CSTCWeaponMine, STCWeaponMine, weapon_mine, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponMustardGrenade, STCWeaponMustardGrenade, weapon_mustardgrenade, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponNarcoGrenade, STCWeaponNarcoGrenade, weapon_narcogrenade, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponNerveGrenade, STCWeaponNerveGrenade, weapon_nervegrenade, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponP90, STCWeaponP90, weapon_p90, kSTCWeaponRifle ); +DECLARE_STC_WEAPON( CSTCWeaponPSG1, STCWeaponPSG1, weapon_psg1, kSTCWeaponSniper ); +DECLARE_STC_WEAPON( CSTCWeaponReetou, STCWeaponReetou, weapon_reetou, kSTCWeaponSupport ); +DECLARE_STC_WEAPON( CSTCWeaponSmokeGrenade, STCWeaponSmokeGrenade, weapon_smokegrenade, kSTCWeaponGrenade ); +DECLARE_STC_WEAPON( CSTCWeaponSPAS, STCWeaponSPAS, weapon_spas, kSTCWeaponShotgun ); +DECLARE_STC_WEAPON( CSTCWeaponTacGun, STCWeaponTacGun, weapon_tacgun, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponUSAS, STCWeaponUSAS, weapon_usas, kSTCWeaponShotgun ); +DECLARE_STC_WEAPON( CSTCWeaponZat, STCWeaponZat, weapon_zat, kSTCWeaponEnergy ); +DECLARE_STC_WEAPON( CSTCWeaponZatArc, STCWeaponZatArc, weapon_zatarc, kSTCWeaponEnergy );