source-engine/game/server/stargate/recovered_entities.cpp
Deploy Bot 44cb9a4837 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>
2026-05-25 14:08:44 +01:00

570 lines
17 KiB
C++

//============= 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.