diff --git a/.gitignore b/.gitignore index 717bd12d..317008c8 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ waf3*/ .vscode/ .depproj/ source-engine.sln +/source-engine-runtime/ diff --git a/game/client/client_hl2mp.vpc b/game/client/client_hl2mp.vpc index aaa6c615..c075efac 100644 --- a/game/client/client_hl2mp.vpc +++ b/game/client/client_hl2mp.vpc @@ -174,6 +174,7 @@ $Project "Client (HL2MP)" $Folder "Stargate" { $File "$SRCDIR\game\shared\stargate\sg_weapons.cpp" + $File "$SRCDIR\game\shared\stargate\sg_mcp_debug_client.cpp" $File "$SRCDIR\game\shared\stargate\sg_constants.h" } } diff --git a/game/server/server_hl2mp.vpc b/game/server/server_hl2mp.vpc index e101a4d2..7226ec93 100644 --- a/game/server/server_hl2mp.vpc +++ b/game/server/server_hl2mp.vpc @@ -302,6 +302,7 @@ $Project "Server (HL2MP)" $File "stargate\ring_transporter.cpp" $File "stargate\sg_event.cpp" $File "stargate\sg_kawoosh.cpp" + $File "stargate\sg_mcp_debug.cpp" $File "stargate\sg_player_spawn.cpp" $File "stargate\STUBS.cpp" $File "$SRCDIR\game\shared\stargate\sg_weapons.cpp" diff --git a/game/server/stargate/sg_mcp_debug.cpp b/game/server/stargate/sg_mcp_debug.cpp new file mode 100644 index 00000000..2d92d4dc --- /dev/null +++ b/game/server/stargate/sg_mcp_debug.cpp @@ -0,0 +1,538 @@ +//============= Copyright (c) StargateTC Source Team ========================== +// +// MCP debug console commands. These are designed to be called via RCON from +// the tools/mcp_source_engine/ Python MCP server. They emit structured output +// (one JSON-ish line per record) that is easy to parse from the other side. +// +// Server-side only. +// +//============================================================================= +#include "cbase.h" +#include +#include "baseentity.h" +#include "player.h" +#include "hl2mp/hl2mp_gamerules.h" +#include "team.h" +#include "in_buttons.h" + +#include "tier0/memdbgon.h" + +static int SG_CountPlayers() +{ + int n = 0; + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + if ( UTIL_PlayerByIndex( i ) ) ++n; + } + return n; +} + +//----------------------------------------------------------------------------- +// sg_list_entities [class_filter] +// Emits one line per live entity: +// ENT idx=N class= name= origin=x,y,z +//----------------------------------------------------------------------------- +CON_COMMAND( sg_list_entities, "Dump server entity table (one line per entity)." ) +{ + const char *pFilter = args.ArgC() > 1 ? args.Arg( 1 ) : NULL; + + int nMax = gpGlobals->maxEntities; + int nCount = 0; + for ( int i = 0; i < nMax; ++i ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( engine->PEntityOfEntIndex( i ) ); + if ( !pEnt ) continue; + + const char *pClass = pEnt->GetClassname(); + if ( pFilter && pFilter[0] && !Q_stristr( pClass, pFilter ) ) + continue; + + Vector v = pEnt->GetAbsOrigin(); + const char *pName = STRING( pEnt->GetEntityName() ); + Msg( "ENT idx=%d class=%s name=%s origin=%.1f,%.1f,%.1f\n", + i, pClass, pName && pName[0] ? pName : "-", v.x, v.y, v.z ); + ++nCount; + } + Msg( "ENT_COUNT %d\n", nCount ); +} + +//----------------------------------------------------------------------------- +// sg_dump_entity +// Verbose dump of one entity: class, name, health, origin, angles, model. +//----------------------------------------------------------------------------- +CON_COMMAND( sg_dump_entity, "Dump details for one server entity by index." ) +{ + if ( args.ArgC() < 2 ) + { + Msg( "Usage: sg_dump_entity \n" ); + return; + } + int idx = atoi( args.Arg( 1 ) ); + edict_t *pEdict = engine->PEntityOfEntIndex( idx ); + CBaseEntity *pEnt = pEdict ? CBaseEntity::Instance( pEdict ) : NULL; + if ( !pEnt ) + { + Msg( "DUMP_FAIL idx=%d (no entity)\n", idx ); + return; + } + + Vector origin = pEnt->GetAbsOrigin(); + QAngle ang = pEnt->GetAbsAngles(); + const char *pModel = STRING( pEnt->GetModelName() ); + const char *pName = STRING( pEnt->GetEntityName() ); + + Msg( "DUMP idx=%d class=%s name=%s health=%d/%d origin=%.1f,%.1f,%.1f angles=%.1f,%.1f,%.1f model=%s solid=%d move=%d\n", + idx, + pEnt->GetClassname(), + pName && pName[0] ? pName : "-", + pEnt->GetHealth(), + pEnt->GetMaxHealth(), + origin.x, origin.y, origin.z, + ang.x, ang.y, ang.z, + pModel && pModel[0] ? pModel : "-", + (int)pEnt->GetSolid(), + (int)pEnt->GetMoveType() ); +} + +//----------------------------------------------------------------------------- +// sg_player_info — dump first connected player's state. +//----------------------------------------------------------------------------- +CON_COMMAND( sg_player_info, "Dump player state (first connected)." ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer ) continue; + + Vector v = pPlayer->GetAbsOrigin(); + QAngle a = pPlayer->GetAbsAngles(); + int team = pPlayer->GetTeamNumber(); + const char *pTeamName = pPlayer->GetTeam() ? pPlayer->GetTeam()->GetName() : "none"; + CBaseCombatWeapon *pWep = pPlayer->GetActiveWeapon(); + + Msg( "PLAYER idx=%d name=%s team=%d teamname=%s health=%d armor=%d origin=%.1f,%.1f,%.1f angles=%.1f,%.1f,%.1f weapon=%s\n", + i, + pPlayer->GetPlayerName(), + team, + pTeamName, + pPlayer->GetHealth(), + pPlayer->ArmorValue(), + v.x, v.y, v.z, + a.x, a.y, a.z, + pWep ? pWep->GetClassname() : "-" ); + return; + } + Msg( "PLAYER_NONE\n" ); +} + +//----------------------------------------------------------------------------- +// sg_dump_gamerules — dump CHL2MPRules state. +//----------------------------------------------------------------------------- +CON_COMMAND( sg_dump_gamerules, "Dump Stargate gamerules state." ) +{ + if ( !g_pGameRules ) + { + Msg( "GAMERULES none\n" ); + return; + } + Msg( "GAMERULES class=%s description=%s teamplay=%d multiplayer=%d\n", + typeid( *g_pGameRules ).name(), + g_pGameRules->GetGameDescription(), + (int)g_pGameRules->IsTeamplay(), + (int)g_pGameRules->IsMultiplayer() ); + + // Dump each registered team + extern CUtlVector< CTeam * > g_Teams; + for ( int i = 0; i < g_Teams.Count(); ++i ) + { + CTeam *t = g_Teams[ i ]; + if ( !t ) continue; + Msg( "TEAM idx=%d name=%s score=%d players=%d\n", + i, t->GetName(), t->GetScore(), t->GetNumPlayers() ); + } +} + +//----------------------------------------------------------------------------- +// sg_give_all_stargate — give all 29 Stargate weapons to the calling player. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_give_all_stargate, "Give all Stargate weapons to player (cheat).", FCVAR_CHEAT ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) + { + Msg( "GIVE_FAIL no player\n" ); + return; + } + + static const char *kStargateWeapons[] = { + "weapon_9mmAR", "weapon_batsup", "weapon_c4", "weapon_coujaf", + "weapon_couteau", "weapon_flashbang", "weapon_gacgoa", "weapon_glstaff", + "weapon_goaregen", "weapon_invis", "weapon_lance", "weapon_larve", + "weapon_m16", "weapon_m92s", "weapon_main", "weapon_medic", + "weapon_mine", "weapon_mustardgrenade", "weapon_narcogrenade", + "weapon_nervegrenade", "weapon_p90", "weapon_psg1", "weapon_reetou", + "weapon_smokegrenade", "weapon_spas", "weapon_tacgun", "weapon_usas", + "weapon_zat", "weapon_zatarc" + }; + int nGiven = 0; + for ( int i = 0; i < ARRAYSIZE( kStargateWeapons ); ++i ) + { + pPlayer->GiveNamedItem( kStargateWeapons[ i ] ); + ++nGiven; + } + Msg( "GIVE_ALL_STARGATE count=%d\n", nGiven ); +} + +//----------------------------------------------------------------------------- +// sg_assert — trigger a controlled DebugBreak so we can verify the debugger +// is attached. No-op in release if no debugger. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_debugbreak, "Trigger DebugBreak() — for verifying attached debugger.", FCVAR_CHEAT ) +{ + Msg( "About to DebugBreak — if a debugger is attached it will catch this.\n" ); + DebuggerBreak(); +} + +//----------------------------------------------------------------------------- +// sg_teleport — teleport calling player to absolute coords. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_teleport, "Teleport calling player to .", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 4 ) + { + Msg( "Usage: sg_teleport \n" ); + return; + } + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) + { + Msg( "TELEPORT_FAIL no_player\n" ); + return; + } + Vector v( atof( args.Arg(1) ), atof( args.Arg(2) ), atof( args.Arg(3) ) ); + pPlayer->Teleport( &v, NULL, &vec3_origin ); + Msg( "TELEPORT ok x=%.1f y=%.1f z=%.1f\n", v.x, v.y, v.z ); +} + +//----------------------------------------------------------------------------- +// sg_give — give a single weapon to the calling player. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_give, "Give one weapon by classname.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) + { + Msg( "Usage: sg_give \n" ); + return; + } + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) + { + Msg( "GIVE_FAIL no_player\n" ); + return; + } + const char *pName = args.Arg(1); + pPlayer->GiveNamedItem( pName ); + Msg( "GIVE ok weapon=%s\n", pName ); +} + +//----------------------------------------------------------------------------- +// sg_set_health — set player health. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_set_health, "Set player health.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_set_health \n" ); return; } + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "HEALTH_FAIL no_player\n" ); return; } + int hp = atoi( args.Arg(1) ); + pPlayer->SetHealth( hp ); + Msg( "HEALTH ok hp=%d\n", hp ); +} + +//----------------------------------------------------------------------------- +// sg_set_team — change calling player's team. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_set_team, "Set player team (2=Tau'ri, 3=Goa'uld, 4=Jaffa, 5=Unas).", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_set_team \n" ); return; } + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "TEAM_FAIL no_player\n" ); return; } + int team = atoi( args.Arg(1) ); + pPlayer->ChangeTeam( team ); + Msg( "TEAM ok team=%d name=%s\n", + team, + pPlayer->GetTeam() ? pPlayer->GetTeam()->GetName() : "?" ); +} + +//----------------------------------------------------------------------------- +// sg_force_attack — fire primary attack with the active weapon. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_force_attack, "Make calling player fire primary attack.", FCVAR_CHEAT ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "ATTACK_FAIL no_player\n" ); return; } + CBaseCombatWeapon *pWep = pPlayer->GetActiveWeapon(); + if ( !pWep ) { Msg( "ATTACK_FAIL no_weapon\n" ); return; } + pWep->PrimaryAttack(); + Msg( "ATTACK ok weapon=%s\n", pWep->GetClassname() ); +} + +CON_COMMAND_F( sg_force_attack2, "Make calling player fire secondary attack.", FCVAR_CHEAT ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "ATTACK2_FAIL no_player\n" ); return; } + CBaseCombatWeapon *pWep = pPlayer->GetActiveWeapon(); + if ( !pWep ) { Msg( "ATTACK2_FAIL no_weapon\n" ); return; } + pWep->SecondaryAttack(); + Msg( "ATTACK2 ok weapon=%s\n", pWep->GetClassname() ); +} + +//----------------------------------------------------------------------------- +// sg_kill_self — force kill the calling player. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_kill_self, "Force kill the calling player.", FCVAR_CHEAT ) +{ + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "KILL_FAIL no_player\n" ); return; } + pPlayer->CommitSuicide(); + Msg( "KILL ok\n" ); +} + +//----------------------------------------------------------------------------- +// sg_spawn [x y z] — spawn an entity. Defaults to player position. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_spawn, "Spawn an entity at (or player pos).", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_spawn [x y z]\n" ); return; } + const char *pClass = args.Arg(1); + Vector pos = vec3_origin; + if ( args.ArgC() >= 5 ) + { + pos.Init( atof( args.Arg(2) ), atof( args.Arg(3) ), atof( args.Arg(4) ) ); + } + else + { + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if ( !pPlayer ) { Msg( "SPAWN_FAIL no_player_for_default_pos\n" ); return; } + pos = pPlayer->GetAbsOrigin(); + } + CBaseEntity *pEnt = CreateEntityByName( pClass ); + if ( !pEnt ) { Msg( "SPAWN_FAIL unknown_class=%s\n", pClass ); return; } + pEnt->SetAbsOrigin( pos ); + DispatchSpawn( pEnt ); + pEnt->Activate(); + Msg( "SPAWN ok class=%s idx=%d origin=%.1f,%.1f,%.1f\n", + pClass, pEnt->entindex(), pos.x, pos.y, pos.z ); +} + +//----------------------------------------------------------------------------- +// sg_net_stats — dump server-side networking + tickrate state. +//----------------------------------------------------------------------------- +CON_COMMAND( sg_net_stats, "Dump server networking stats." ) +{ + Msg( "NET_STATS tickcount=%d tickinterval=%.4f curtime=%.2f frametime=%.4f maxClients=%d\n", + gpGlobals->tickcount, + gpGlobals->interval_per_tick, + gpGlobals->curtime, + gpGlobals->frametime, + gpGlobals->maxClients ); + // Per-player edict info + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *p = UTIL_PlayerByIndex( i ); + if ( !p ) continue; + edict_t *e = p->edict(); + Msg( "NET_PLAYER idx=%d name=%s edict_free=%d edict_dirty=%d\n", + i, p->GetPlayerName(), + ( e && e->IsFree() ) ? 1 : 0, + ( e && e->m_fStateFlags & FL_EDICT_DIRTY_PVS_INFORMATION ) ? 1 : 0 ); + } +} + +//----------------------------------------------------------------------------- +// sg_ent_messages — dump entity's I/O message queue / connections. +//----------------------------------------------------------------------------- +CON_COMMAND( sg_ent_messages, "Dump an entity's outputs/inputs." ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_ent_messages \n" ); return; } + int idx = atoi( args.Arg(1) ); + edict_t *e = engine->PEntityOfEntIndex( idx ); + CBaseEntity *pEnt = e ? CBaseEntity::Instance( e ) : NULL; + if ( !pEnt ) { Msg( "ENT_MSG_FAIL idx=%d\n", idx ); return; } + // Report classname + datadesc fields (basic introspection without + // recursing into nested classes; useful as a starting point). + Msg( "ENT_MSG idx=%d class=%s name=%s\n", + idx, + pEnt->GetClassname(), + STRING( pEnt->GetEntityName() ) ); + datamap_t *map = pEnt->GetDataDescMap(); + while ( map ) + { + Msg( "ENT_MSG_MAP class=%s field_count=%d\n", + map->dataClassName ? map->dataClassName : "?", + map->dataNumFields ); + map = map->baseMap; + } +} + +//----------------------------------------------------------------------------- +// sg_set_angles [roll] — set player view angles. +// Use snap_view (no smoothing — instantaneous like the user mouse-looked). +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_set_angles, "Set player view angles [roll].", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 3 ) { Msg( "Usage: sg_set_angles [roll]\n" ); return; } + CBasePlayer *p = UTIL_GetCommandClient(); + if ( !p ) { Msg( "ANGLES_FAIL no_player\n" ); return; } + QAngle a( atof( args.Arg(1) ), atof( args.Arg(2) ), + args.ArgC() >= 4 ? atof( args.Arg(3) ) : 0.0f ); + p->SnapEyeAngles( a ); + Msg( "ANGLES ok pitch=%.1f yaw=%.1f roll=%.1f\n", a.x, a.y, a.z ); +} + +//----------------------------------------------------------------------------- +// sg_step [up] — relative-move the player. +// Uses the player's current forward/right vectors. Performs a collision-safe +// teleport (Source's Teleport sets origin and handles collision internally). +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_step, "Step player by [up] units relative to view.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 3 ) { Msg( "Usage: sg_step [up]\n" ); return; } + CBasePlayer *p = UTIL_GetCommandClient(); + if ( !p ) { Msg( "STEP_FAIL no_player\n" ); return; } + float fwd = atof( args.Arg(1) ); + float rgt = atof( args.Arg(2) ); + float up = args.ArgC() >= 4 ? atof( args.Arg(3) ) : 0.0f; + Vector vForward, vRight, vUp; + AngleVectors( p->EyeAngles(), &vForward, &vRight, &vUp ); + Vector newPos = p->GetAbsOrigin() + vForward * fwd + vRight * rgt + vUp * up; + p->Teleport( &newPos, NULL, &vec3_origin ); + Msg( "STEP ok origin=%.1f,%.1f,%.1f\n", newPos.x, newPos.y, newPos.z ); +} + +//----------------------------------------------------------------------------- +// sg_aim_at — orient player to look at a world position. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_aim_at, "Aim player at world position .", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 4 ) { Msg( "Usage: sg_aim_at \n" ); return; } + CBasePlayer *p = UTIL_GetCommandClient(); + if ( !p ) { Msg( "AIM_FAIL no_player\n" ); return; } + Vector target( atof( args.Arg(1) ), atof( args.Arg(2) ), atof( args.Arg(3) ) ); + Vector dir = target - p->EyePosition(); + QAngle ang; + VectorAngles( dir, ang ); + p->SnapEyeAngles( ang ); + Msg( "AIM ok target=%.1f,%.1f,%.1f angles=%.1f,%.1f,%.1f\n", + target.x, target.y, target.z, ang.x, ang.y, ang.z ); +} + +//----------------------------------------------------------------------------- +// sg_aim_at_entity — aim player at the origin of an entity. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_aim_at_entity, "Aim player at entity by index.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_aim_at_entity \n" ); return; } + CBasePlayer *p = UTIL_GetCommandClient(); + if ( !p ) { Msg( "AIM_FAIL no_player\n" ); return; } + int idx = atoi( args.Arg(1) ); + edict_t *e = engine->PEntityOfEntIndex( idx ); + CBaseEntity *pTarget = e ? CBaseEntity::Instance( e ) : NULL; + if ( !pTarget ) { Msg( "AIM_FAIL no_entity idx=%d\n", idx ); return; } + Vector dir = pTarget->WorldSpaceCenter() - p->EyePosition(); + QAngle ang; + VectorAngles( dir, ang ); + p->SnapEyeAngles( ang ); + Msg( "AIM ok target_idx=%d class=%s angles=%.1f,%.1f,%.1f\n", + idx, pTarget->GetClassname(), ang.x, ang.y, ang.z ); +} + +//----------------------------------------------------------------------------- +// sg_use_weapon — switch active weapon. +//----------------------------------------------------------------------------- +CON_COMMAND_F( sg_use_weapon, "Switch active weapon by classname.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) { Msg( "Usage: sg_use_weapon \n" ); return; } + CBasePlayer *p = UTIL_GetCommandClient(); + if ( !p ) { Msg( "USEWEAPON_FAIL no_player\n" ); return; } + const char *pName = args.Arg(1); + CBaseCombatWeapon *pWep = p->Weapon_OwnsThisType( pName ); + if ( !pWep ) + { + // Try to give it first, then switch + p->GiveNamedItem( pName ); + pWep = p->Weapon_OwnsThisType( pName ); + } + if ( !pWep ) { Msg( "USEWEAPON_FAIL not_owned=%s\n", pName ); return; } + p->Weapon_Switch( pWep ); + Msg( "USEWEAPON ok weapon=%s\n", pName ); +} + +//----------------------------------------------------------------------------- +// sg_press_button