Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Download all plugins in a zip file here: <a href="https://sourcemod.krus.dk/f2-s
- Logs players spawning
- Logs game pauses
- Logs shots fired and shots hit (for certain weapons)
- Logs unique match id, map name and title at the start of the match

### LogsTF <a href="https://sourcemod.krus.dk/logstf.zip"><img src="https://img.shields.io/badge/-download-informational" /></a> <a href="./logstf"><img src="https://img.shields.io/badge/-More%20info-yellowgreen" /></a>

Expand Down
20 changes: 15 additions & 5 deletions includes/match.inc
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ Remember to call:

Then use these functions:

StartMatch() {
void StartMatch() {
}

ResetMatch() {
void ResetMatch() {
}

EndMatch(bool:endedMidgame) {
void EndMatch(bool endedMidgame) {
}

Optional:

public void StartFirstRound() {
}

*/

/*
TODO:
- Dont call StartMatch before the 5-4-3-2-1 countdown reaches 0.
- Make a new callback, AllPlayersReady
- Make a new callback, AboutToEnd (5 secs before MatchEnded)
- g_fMatchEndTime
- g_bCountdownStarted
Expand Down Expand Up @@ -156,6 +160,12 @@ public void Match_Event_round_start(Event event, const char[] name, bool dontBro

if (!g_bFirstRoundStarted) {
g_bFirstRoundStarted = true;

Function func = GetFunctionByName(null, "StartFirstRound");
if (func != null) {
Call_StartFunction(null, func);
Call_Finish();
}
}
}
}
Expand Down
48 changes: 46 additions & 2 deletions logs-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ When we write `<player>`, `<attacker>` or similar, they refer to this format:

"F2<3><[U:1:1234]><Red>"

# Supplemental logs (supstats2)
# Supplemental logs

## Pauses

Expand Down Expand Up @@ -158,7 +158,7 @@ For Crusader's Crossbow, friendly hits will be logged as `shot_hit`.

Destroying a sticky with a hitscan weapon will be logged as `shot_hit` (although it might not always be accurately detected).

# Medic Stats (medicstats)
# Medic Stats

**A note on vaccinator:** These logs are very bugged for vaccinator. For example, "charge ready" is only logged at 100%, but with vaccinator you can use charge at 25%. And it does not make sense talking about "uber advantage lost" when one team has uber and the other vaccinator. So, if one team uses vaccinator, think carefully about how you use these logs.

Expand Down Expand Up @@ -221,3 +221,47 @@ When one team gets uber significantly before the other team, but does not use it
"F2<3><[U:1:1234]><Red>" triggered "lost_uber_advantage" (time "14")

Alternatively, this could also be computed by looking at the "chargeready" and "chargedeployed" logs from both teams.

# Meta data

Sometimes we want to write information to the logs that are not directly triggered by in-game events. We call this _meta data_.

When it comes to these meta logs, **all properties are optional**.

More properties can be added in the future, but the format of the ones listed should not be changed.

## Player related

> [!WARNING]
> Player related logs have not been finalized, and parsers should not implement this yet.

To write meta data that is directly related to a player:

<player> triggered "meta_data" (position "%i %i %i")

"F2<3><[U:1:1234]><Red>" triggered "meta_data" (position "478 -334 603")

It can have the following properties:

- `position`: The current location of the player

## Non-player related

To write meta data that is not directly related to a player:

World triggered "meta_data" (matchid "%s") (map "%s") (title "%s")

World triggered "meta_data" (matchid "abc123") (map "cp_badlands") (title "serveme.tf #1337")

It can have the following properties:

- `matchid`: A randomly generated match id, hopefully unique (max 32 alphanumeric chars).
This can be used to detect duplicate logs from the same match.
- `map`: The current map
- `title`: A title for the match (similar to the one sent to logs.tf)

There can be multiple of these log lines in a match, so for example you might see this:

World triggered "meta_data" (matchid "abc123")
World triggered "meta_data" (map "cp_badlands")
World triggered "meta_data" (title "serveme.tf #1337")
14 changes: 11 additions & 3 deletions logstf/logstf.sp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ Release notes:
- Updated code to be compatible with SourceMod 1.12


---- 2.7.0 (22/07/2025) ----
- Changed match title trimming logic


TODO:
- Some people run multiple instances of the same server (located in the same directory). This is a problem, because they all write to the same logstf.log file. Make the logstf.log and -partial files have dynamic names, and don't forget to clean them up.
- Sanitize names for < and >, since logs.tf doesn't like those
Expand All @@ -146,7 +150,7 @@ TODO:
#undef REQUIRE_PLUGIN
#include <updater>

#define PLUGIN_VERSION "2.6.6"
#define PLUGIN_VERSION "2.7.0"
#define UPDATE_URL "https://sourcemod.krus.dk/logstf/update.txt"

#define LOG_PATH "logstf.log"
Expand Down Expand Up @@ -364,6 +368,8 @@ void EndMatch(bool endedMidgame) {
}

void CacheMatchValues() {
// Note: If you are making changes related to title generation, also update supstats2.

g_hCvarHostname.GetString(g_sCachedHostname, sizeof(g_sCachedHostname));
g_hCvarBlueTeamName.GetString(g_sCachedBluTeamName, sizeof(g_sCachedBluTeamName));
g_hCvarRedTeamName.GetString(g_sCachedRedTeamName, sizeof(g_sCachedRedTeamName));
Expand All @@ -372,9 +378,10 @@ void CacheMatchValues() {
String_Trim(g_sCachedRedTeamName, g_sCachedRedTeamName, sizeof(g_sCachedRedTeamName));
GetCurrentMap(g_sCachedMap, sizeof(g_sCachedMap));

// Remove last word in hostname
// Trim the last words in hostname if it is a long hostname
int spacepos = -1;
for (int i = strlen(g_sCachedHostname) - 1; i >= 17; i--) {
int hostnameLen = strlen(g_sCachedHostname);
for (int i = 25; i < hostnameLen; i++) {
if (g_sCachedHostname[i] == ' ') {
spacepos = i;
break;
Expand Down Expand Up @@ -690,6 +697,7 @@ void UploadLog(bool partial) {
g_bIsUploading = true;
g_bIsPartialUpload = partial;

// Note: If you are making changes related to title generation, also update supstats2.
char title[128];
g_hCvarTitle.GetString(title, sizeof(title));
ReplaceString(title, sizeof(title), "{server}", g_sCachedHostname, false);
Expand Down
6 changes: 3 additions & 3 deletions logstf/update.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
{
"Version"
{
"Latest" "2.6.6"
"Latest" "2.7.0"
}

"Notes" "Changes in 2.6.6:"
"Notes" "- Updated code to be compatible with SourceMod 1.12"
"Notes" "Changes in 2.7.0:"
"Notes" "- Changed match title trimming logic"
}

"Files"
Expand Down
8 changes: 5 additions & 3 deletions supstats2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ Features:
- Logs players spawning
- Logs game pauses
- Logs shots fired and shots hit (for certain weapons)
- Logs unique match id, map name and title at the start of the match

| CVAR | Default | Description |
| -------------------------- | ------- | -------------------- |
| `supstats_accuracy <0\|1>` | `1` | Enable accuracy logs |
| CVAR | Default | Description |
| -------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `supstats_accuracy <0\|1>` | `1` | Enable accuracy logs |
| `logstf_title <title>` | `{server}: {blu} vs {red}` | Sets the title of the log, which is logged at the start of the match.<br>Use `{server}` to insert the server title.<br>Use `{blu}` or `{red}` to insert team names. |
131 changes: 129 additions & 2 deletions supstats2/supstats2.sp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ Release notes:
- Updated code to be compatible with SourceMod 1.12


---- 2.6.0 (19/07/2025) ----
- Log meta data at the start of the match (matchid, map, title)


TODO:
- Use GetGameTime() instead of GetEngineTime()?
- Write comments in code :D
Expand All @@ -108,10 +112,11 @@ TODO:
#include <sdkhooks>
#include <smlib>
#include <kvizzle>
#include <match>
#undef REQUIRE_PLUGIN
#include <updater>

#define PLUGIN_VERSION "2.5.4"
#define PLUGIN_VERSION "2.6.0"
#define UPDATE_URL "https://sourcemod.krus.dk/supstats2/update.txt"

#define NAMELEN 64
Expand Down Expand Up @@ -150,6 +155,12 @@ int medpackHealAmount[MAXPLAYERS+1];
float g_fPauseStartTime;
char g_sTauntNames[][] = { "", "taunt_scout", "taunt_sniper", "taunt_soldier", "taunt_demoman", "taunt_medic", "taunt_heavy", "taunt_pyro", "taunt_spy", "taunt_engineer" };

Handle g_hTimerMatchStarted = null;
ConVar g_hCvarLogsTitle = null;
ConVar g_hCvarHostname = null,
g_hCvarRedTeamName = null,
g_hCvarBlueTeamName = null;


// ---- ACCURACY ----
ConVar g_hCvarEnableAccuracy = null;
Expand Down Expand Up @@ -195,6 +206,7 @@ public void OnPluginStart() {
if (LibraryExists("updater"))
Updater_AddPlugin(UPDATE_URL);

Match_OnPluginStart();

g_tShotTypes = CreateTrie();
g_tShotTypes.SetValue("tf_weapon_rocketlauncher", SHOT_ROCKET);
Expand Down Expand Up @@ -228,8 +240,15 @@ public void OnPluginStart() {
char cvarEnableAccuracy[16];
g_hCvarEnableAccuracy.GetString(cvarEnableAccuracy, sizeof(cvarEnableAccuracy));
CvarChange_EnableAccuracy(g_hCvarEnableAccuracy, cvarEnableAccuracy, cvarEnableAccuracy);

// The "logstf_" prefix is used because we are reusing the cvar from the logstf plugin.
// This is done to avoid having all servers defining the title format twice.
g_hCvarLogsTitle = CreateConVar("logstf_title", "{server}: {blu} vs {red}", "Title for the log", FCVAR_NONE);
g_hCvarHostname = FindConVar("hostname");
g_hCvarRedTeamName = FindConVar("mp_tournament_redteamname");
g_hCvarBlueTeamName = FindConVar("mp_tournament_blueteamname");

char map[64];
char map[128];
GetCurrentMap(map, sizeof(map));
LogToGame("Loading map \"%s\"", map);

Expand Down Expand Up @@ -271,6 +290,8 @@ public void OnLibraryAdded(const char[] name) {
}

public void OnMapStart() {
Match_OnMapStart();

for (int i = 0; i < sizeof(g_iIgnoreDamageEnt); i++)
g_iIgnoreDamageEnt[i] = 0;

Expand All @@ -281,6 +302,10 @@ public void OnMapStart() {
g_bIsPaused = false; // The game is automatically unpaused during a map change
}

public void OnMapEnd() {
Match_OnMapEnd();
}

public void OnClientPutInServer(int client) {
if (!g_bWeaponDataInitialized)
return;
Expand Down Expand Up @@ -359,6 +384,108 @@ public Action CheckPause(Handle timer, int client) {
return Plugin_Continue;
}

void StartMatch() {
}

public void StartFirstRound() {
if (g_hTimerMatchStarted != null) {
delete g_hTimerMatchStarted;
g_hTimerMatchStarted = null;
}

// First log all the players spawning etc., and then we log the meta data.
g_hTimerMatchStarted = CreateTimer(0.5, LogMetaData);
}

void ResetMatch() {
if (g_hTimerMatchStarted != null) {
delete g_hTimerMatchStarted;
g_hTimerMatchStarted = null;
}
}

void EndMatch(bool endedMidgame) {
if (g_hTimerMatchStarted != null) {
delete g_hTimerMatchStarted;
g_hTimerMatchStarted = null;
}
}

void LogMetaData(Handle timer) {
g_hTimerMatchStarted = null;

LogMatchId();
LogMapName();
LogTitle();
}

void LogMatchId() {
char matchId[33];
FormatTime(matchId, sizeof(matchId), "%y%m%d%H%M%S");

for (int i = 12; i < 32; i++) {
int randVal = GetRandomInt(0, 61);
if (randVal < 10) {
matchId[i] = '0' + randVal;
} else if (randVal < 36) {
matchId[i] = 'a' + (randVal - 10);
} else {
matchId[i] = 'A' + (randVal - 36);
}
}
matchId[32] = '\0';

LogToGame("World triggered \"meta_data\" (matchid \"%s\")", matchId);
}

void LogMapName() {
char currentMap[128];
GetCurrentMap(currentMap, sizeof(currentMap));
LogToGame("World triggered \"meta_data\" (map \"%s\")", currentMap);
}

void LogTitle() {
// Note: If you are making changes related to title generation, also update logstf.

char hostname[64];
char bluTeamName[32];
char redTeamName[32];

g_hCvarHostname.GetString(hostname, sizeof(hostname));
g_hCvarBlueTeamName.GetString(bluTeamName, sizeof(bluTeamName));
g_hCvarRedTeamName.GetString(redTeamName, sizeof(redTeamName));
String_Trim(hostname, hostname, sizeof(hostname));
String_Trim(bluTeamName, bluTeamName, sizeof(bluTeamName));
String_Trim(redTeamName, redTeamName, sizeof(redTeamName));

// Trim the last words in hostname if it is a long hostname
int spacepos = -1;
int hostnameLen = strlen(hostname);
for (int i = 25; i < hostnameLen; i++) {
if (hostname[i] == ' ') {
spacepos = i;
break;
}
}

if (spacepos != -1) {
hostname[spacepos] = '\0';
String_Trim(hostname, hostname, sizeof(hostname), " -:.!,;");
}

// Replace placeholders
char title[128];
g_hCvarLogsTitle.GetString(title, sizeof(title));
ReplaceString(title, sizeof(title), "{server}", hostname, false);
ReplaceString(title, sizeof(title), "{blu}", bluTeamName, false);
ReplaceString(title, sizeof(title), "{blue}", bluTeamName, false);
ReplaceString(title, sizeof(title), "{red}", redTeamName, false);

ReplaceString(title, sizeof(title), "\\", "", false);

LogToGame("World triggered \"meta_data\" (title \"%s\")", title);
}

public Action Event_PlayerHealed(Event event, const char[] name, bool dontBroadcast) {
char patientName[NAMELEN];
char healerName[NAMELEN];
Expand Down
Loading