/** * [CSRD] Soldier Statue Spawner * * Provides some tooling to spawn Rick May tribute statues on maps that don't support it. * Only available when the holiday is active. */ #pragma semicolon 1 #include #include #pragma newdecls required #include #include #include #define PLUGIN_VERSION "1.0.0" public Plugin myinfo = { name = "[CSRD] Soldier Statue Spawner", author = "nosoop", description = "Spawns the Rick May tribute statue in custom locations.", version = PLUGIN_VERSION, url = "https://git.csrd.science/" } // CREATE TABLE IF NOT EXIST locations (map TEXT NOT NULL UNIQUE, xpos REAL, ypos REAL, zpos REAL, pitchang REAL, yawang REAL, rollang REAL); Database g_SpawnLocations; public void OnPluginStart() { RegAdminCmd("csrd_statue_place_aim", CommandSetPositionAim, ADMFLAG_ROOT); RegAdminCmd("csrd_statue_reload", ReloadStatuePosition, ADMFLAG_ROOT); char error[128]; g_SpawnLocations = SQLite_UseDatabase("soldier_statues", error, sizeof(error)); if (!g_SpawnLocations) { SetFailState("Failed to open soldier_statues database: %s", error); } } public void OnMapStart() { if (!TF2_IsHolidayActive(0x0C)) { return; } char mapName[PLATFORM_MAX_PATH]; GetCurrentMapDisplayName(mapName, sizeof(mapName)); char query[256]; g_SpawnLocations.Format(query, sizeof(query), "SELECT xpos, ypos, zpos, pitchang, yawang, rollang " ... "FROM locations WHERE map == '%s';", mapName); g_SpawnLocations.Query(OnSpawnLocationLoaded, query); } public Action CommandSetPositionAim(int client, int argc) { float position[3], origin[3]; GetClientAbsOrigin(client, origin); GetClientAimEndPosition(client, position); float direction[3], angles[3]; MakeVectorFromPoints(position, origin, direction); GetVectorAngles(direction, angles); SetStatuePosition(position, angles); return Plugin_Handled; } public Action ReloadStatuePosition(int client, int argc) { char mapName[PLATFORM_MAX_PATH]; GetCurrentMapDisplayName(mapName, sizeof(mapName)); char query[256]; g_SpawnLocations.Format(query, sizeof(query), "SELECT xpos, ypos, zpos, pitchang, yawang, rollang " ... "FROM locations WHERE map == '%s';", mapName); g_SpawnLocations.Query(OnSpawnLocationLoaded, query); return Plugin_Handled; } void SetStatuePosition(const float position[3], const float angles[3]) { TeleportEntity(GetSoldierStatueEntity(), position, angles, NULL_VECTOR); char mapName[PLATFORM_MAX_PATH]; GetCurrentMapDisplayName(mapName, sizeof(mapName)); char query[256]; g_SpawnLocations.Format(query, sizeof(query), "REPLACE INTO locations(map, xpos, ypos, zpos, pitchang, yawang, rollang)" ... "VALUES ('%s', %f, %f, %f, %f, %f, %f);", mapName, position[0], position[1], position[2], angles[0], angles[1], angles[2]); g_SpawnLocations.Query(OnSpawnLocationSaved, query); } int GetSoldierStatueEntity() { int statue = FindEntityByClassname(-1, "entity_soldier_statue"); if (!IsValidEntity(statue)) { statue = CreateEntityByName("entity_soldier_statue"); DispatchSpawn(statue); } return statue; } public void OnSpawnLocationLoaded(Database db, DBResultSet results, const char[] error, any data) { if (!results.FetchRow()) { if (FindEntityByClassname(-1, "entity_soldier_statue") == -1) { char mapName[PLATFORM_MAX_PATH]; GetCurrentMapDisplayName(mapName, sizeof(mapName)); LogServer("%s does not have a Soldier statue", mapName); } return; } float position[3], angles[3]; position[0] = results.FetchFloat(0); position[1] = results.FetchFloat(1); position[2] = results.FetchFloat(2); angles[0] = results.FetchFloat(3); angles[1] = results.FetchFloat(4); angles[2] = results.FetchFloat(5); TeleportEntity(GetSoldierStatueEntity(), position, angles, NULL_VECTOR); LogServer("Spawned Soldier statue"); } public void OnSpawnLocationSaved(Database db, DBResultSet results, const char[] error, any data) { return; } /** * Obtains the end position in the world that a client is looking at. * * @param client The client to check. * @param vecAimPoint The buffer to store the position in the world on success. * @param filter The trace entity filter function to call when performing the check. * @param data A value to pass into the filter function callback. * * @return true if a valid position is found */ stock bool GetClientAimEndPosition(int client, float vecAimPoint[3], TraceEntityFilter filter = INVALID_FUNCTION, any data = 0) { float angEye[3], vecStartPosition[3]; GetClientEyePosition(client, vecStartPosition); GetClientEyeAngles(client, angEye); Handle trace = TR_TraceRayFilterEx(vecStartPosition, angEye, MASK_SHOT, RayType_Infinite, filter != INVALID_FUNCTION? filter : TraceEntityFilterPlayer, data); bool result = TR_DidHit(trace); if (TR_DidHit(trace)) { TR_GetEndPosition(vecAimPoint, trace); } delete trace; return result; } /** * Returns a trace ray handle in the client's aim direction. */ stock Handle GetClientAimEndTrace(int client, int flags, TraceEntityFilter filter = INVALID_FUNCTION, any data = 0) { float angEye[3], vecStartPosition[3]; GetClientEyePosition(client, vecStartPosition); GetClientEyeAngles(client, angEye); return TR_TraceRayFilterEx(vecStartPosition, angEye, flags, RayType_Infinite, filter != INVALID_FUNCTION? filter : TraceEntityFilterPlayer, data); } /** * Internal callback function that ignores player entities. */ static stock bool TraceEntityFilterPlayer(int entity, int contentsMask) { return entity > MaxClients; }