StargateTC port — initial integration into nillerusr/source-engine

P1: Source files
- Copy 9 server stargate files + sg_constants.h + sg_weapons.cpp from
  StargateTCSource/sourcecode/ into game/server/stargate/ and
  game/shared/stargate/.
- Add $Folder "Stargate" block to server_hl2mp.vpc and client_hl2mp.vpc.
- Extend $AdditionalIncludeDirectories with stargate paths.

P2/P3: Gamerules + player patches
- hl2mp_gamerules.h: add TEAM_JAFFA=4, TEAM_UNAS=5 to team enum.
- hl2mp_gamerules.cpp: preserve 5 Stargate spawn classnames in
  s_PreserveEnts[]; rename teams to Tau'ri/Goa'uld/Jaffa/Unas;
  rename game description to "StargateTC"/"StargateTC Teamplay".
- hl2mp_player.cpp: EntSelectSpawnPoint now searches Stargate spawn
  classnames first (torri/goauld/marine/unas) with per-team fallback
  chain to combine/rebel/deathmatch.

Build: 0 errors. Server.dll 13.19MB, client.dll 9.85MB.
Runtime test: window title "StargateTC", live game launches,
HEALTH 100 HUD renders, HL2MP MOTD shown, sky/world geometry renders.
Response system loads 217 rules without crashing (was the original
Mapbase+VS2022 heap-corruption point).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Deploy Bot 2026-05-25 14:08:44 +01:00
parent ed8209cc35
commit 44cb9a4837
16 changed files with 1687 additions and 13 deletions

View File

@ -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"
}
}
}

View File

@ -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;
}
}
}

View File

@ -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"
}
}
}

View File

@ -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 <classname> 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 );

View File

@ -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 );
}

View File

@ -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 );
}

View File

@ -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<GateTarget> 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<float>( 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 );

View File

@ -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.

View File

@ -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 );
}

View File

@ -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 );
}

View File

@ -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 );
}

View File

@ -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 );

View File

@ -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 )
{

View File

@ -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,
};

View File

@ -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

View File

@ -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 );