Add 7 tutorials (7 files)
Browse Source
7 changed files
with 84 additions and 0 deletions
A
automate-vip-payments/en.md
+14 -0
A
fix-failed-to-open-metamod-so/en.md
+18 -0
A
fix-mysql-access-denied-amxx/en.md
+14 -0
A
fix-rtv-not-working/en.md
+9 -0
A
fix-sv-downloadurl-ignored/en.md
+10 -0
A
gametracker-claim-server/en.md
+9 -0
A
track-player-counts-peak-hours/en.md
+10 -0
automate-vip-payments/en.md
A
| @@ -0,0 +1,14 @@ | ||
| 1 | +--- | |
| 2 | +title: "Automating VIP Payments (PayPal/Stripe to Server Flags)" | |
| 3 | +excerpt: "How to turn a completed PayPal or Stripe payment into a working VIP flag on your CS 1.6 server automatically, the webhook-to-database pipeline, and the SteamID trap that breaks it." | |
| 4 | +category: "hosting" | |
| 5 | +tags: "vip, payments" | |
| 6 | +image: "" | |
| 7 | +read_time: "9 min read" | |
| 8 | +--- | |
| 9 | +<p>Selling VIP by hand — a player PayPals you, you SSH in and edit <code>users.ini</code> — works for five customers and collapses at fifty. The goal is a pipeline where a completed payment grants VIP with no admin in the loop and revokes it when it lapses. This explains the architecture that actually works on GoldSrc, the pieces you need, and the SteamID mistake that silently breaks the whole thing. It is a design guide, not a copy-paste script, because the payment side is yours to build safely.</p><h2>1. Understand the pipeline</h2><p>There is no way for PayPal to write to a CS 1.6 server directly. Every working setup routes through a database that both your website and your server can read:</p><ol><li>Player pays on your site and provides their SteamID.</li><li>The payment provider calls your <strong>webhook</strong> to confirm the payment completed.</li><li>Your webhook writes a VIP row — SteamID, tier, expiry date — into a MySQL table.</li><li>An AMX Mod X plugin on the server reads that table when the player connects and grants the VIP flags.</li></ol><p>The database is the bridge. The website never touches the game server; the game server never touches PayPal. They only share the table.</p><h2>2. Build the VIP table</h2><p>Keep it minimal and keyed by SteamID:</p><pre>CREATE TABLE vip_users ( | |
| 10 | + steamid VARCHAR(32) PRIMARY KEY, | |
| 11 | + tier VARCHAR(16) NOT NULL, | |
| 12 | + expires DATETIME NOT NULL, | |
| 13 | + active TINYINT DEFAULT 1 | |
| 14 | +);</pre><p>The <code>expires</code> column is what lets VIP lapse automatically — the plugin only grants access while <code>expires</code> is in the future. No cron job needs to delete rows; the plugin simply checks the date on connect.</p><h2>3. Handle the webhook, not the redirect</h2><p>The single most important payment rule: grant VIP from the provider's <strong>server-to-server webhook</strong> (PayPal IPN/webhooks, Stripe webhook events), never from the browser redirect after checkout. A redirect can be forged or replayed by anyone; the webhook is signed and comes from the provider. On Stripe, verify the event signature; on PayPal, verify the notification against their API before you trust it. Only after the payment is verified <em>completed</em> do you <code>INSERT</code> or update the <code>vip_users</code> row. This is a security boundary, not an optimisation — skipping it means anyone who reaches your success URL gets free VIP.</p><h2>4. Read the table from a plugin</h2><p>On the server, an AMX Mod X plugin using the <code>sqlx</code> module queries the table when a player connects and, if a valid unexpired row exists, adds the VIP flags for that session. The correct way to run that query without freezing the server is a threaded query — see <a href="/tutorials/sqlx-threaded-queries">threaded SQL queries</a>, because a blocking query on connect stalls everyone. The flag-granting itself uses the standard admin flag system; our <a href="/plugins/csb-vip-menu">CSB VIP Menu</a> and the <a href="/tutorials/build-vip-plugin">build a VIP plugin</a> guide show the flag logic, and <a href="/tutorials/amxx-admin-flags-reference">the flags reference</a> explains what each letter grants. Grant flags per session on connect rather than writing them to <code>users.ini</code>, so an expired VIP simply stops being granted with no file edit.</p><h2>5. The SteamID trap</h2><p>The pipeline lives or dies on one join: the SteamID the player typed on your website must match the SteamID the server sees. Two things break this. First, format — a player might give <code>STEAM_0:1:12345</code> while your server, running <a href="/tutorials/install-reunion">Reunion</a> for non-Steam clients, reports a different ID scheme; store and compare in one canonical form. Second, non-Steam ID collisions — if two non-Steam players end up with the same ID, one buys VIP and the other gets it free. This is why a paid VIP system is far safer on a Steam-only server, or on a non-Steam server only once Reunion is configured to hand out stable IDs. Always confirm the exact ID the server sees with <a href="/tutorials/find-player-steamid">the SteamID finder</a> before debugging anything else — a mismatched ID looks exactly like "the payment did not work."</p><h2>Common errors</h2><ul><li><strong>Player paid but has no VIP in-game</strong> — the SteamID on the site does not match what the server sees. Compare both in one format.</li><li><strong>Anyone can get free VIP</strong> — you granted from the browser redirect, not the verified webhook. Move the grant behind signature verification.</li><li><strong>Server lags every time a VIP connects</strong> — a blocking SQL query on connect. Switch to a threaded query.</li><li><strong>VIP never expires</strong> — the plugin ignores the <code>expires</code> column, or grants persist by writing <code>users.ini</code>. Grant per session and check the date on connect.</li><li><strong>Two players share one VIP</strong> — colliding non-Steam IDs. Configure Reunion for stable IDs or restrict paid VIP to Steam clients.</li></ul><h2>Verification</h2><p>Run one real low-value transaction end to end. Pay, watch the webhook fire and insert the row (check the table directly), then connect to the server with that SteamID and confirm the VIP flags are active — check with the admin <code>amx_who</code> command that your access letters appear. Then set that row's <code>expires</code> to a past date, reconnect, and confirm VIP is gone with no file edit on your part. When both the grant and the lapse happen automatically, the pipeline is trustworthy enough to sell.</p> |
fix-failed-to-open-metamod-so/en.md
A
| @@ -0,0 +1,18 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: 'Failed to open metamod.so' (Wrong Path or Arch)" | |
| 3 | +excerpt: "Why Metamod fails to load on Linux, the liblist.gam gamedll_linux path, 32-bit library requirements, Metamod vs Metamod-r, file permissions, and confirming the load with meta list." | |
| 4 | +category: "errors" | |
| 5 | +tags: "metamod, linux" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>Your server logs show a line like:</p><blockquote>Failed to open metamod.so: cannot open shared object file</blockquote><p>or the engine simply skips Metamod and AMX Mod X never loads. Metamod is the layer the engine loads <em>as</em> the game DLL; it then loads the real game DLL plus its own plugins (AMX Mod X among them). If Metamod itself will not open, nothing above it runs. On Linux this error is almost always a wrong path, a missing 32-bit dependency, or a permissions problem. Here is the fix.</p><h2>1. Check the path in liblist.gam</h2><p>With Metamod installed, <code>cstrike/liblist.gam</code> must point its <code>gamedll_linux</code> at Metamod's shared object, not the stock game DLL:</p><pre>gamedll_linux "addons/metamod/dlls/metamod_i386.so" | |
| 10 | +</pre><p>If that path is wrong — a typo, the wrong folder, or a filename that does not match what is on disk — the engine reports it cannot open <code>metamod.so</code>. Confirm the file is exactly where the line says:</p><pre>ls -l cstrike/addons/metamod/dlls/metamod_i386.so</pre><p>The path in <code>liblist.gam</code> is relative to the <code>cstrike/</code> game directory. A mismatch between the filename here and the actual file is the number-one cause.</p><h2>2. Install the 32-bit libraries</h2><p>Metamod is a 32-bit shared object. On a 64-bit server without i386 runtime libraries, <code>dlopen</code> fails even though the file exists — the loader cannot satisfy its dependencies. Install them (Debian/Ubuntu):</p><pre>sudo dpkg --add-architecture i386 | |
| 11 | +sudo apt-get update | |
| 12 | +sudo apt-get install -y libc6:i386 libstdc++6:i386 lib32gcc-s1 | |
| 13 | +</pre><p>Confirm the object is 32-bit and see what it needs:</p><pre>file cstrike/addons/metamod/dlls/metamod_i386.so | |
| 14 | +ldd cstrike/addons/metamod/dlls/metamod_i386.so</pre><p><code>ldd</code> lists any <code>not found</code> dependency — that is your missing 32-bit lib. This overlaps with <a href="/tutorials/fix-libstdcpp-so-6-not-found">the libstdc++ error</a>, the same root cause seen from a different angle.</p><h2>3. Match Metamod flavour to your engine</h2><p>There are two Metamods: classic <strong>Metamod</strong> and <strong>Metamod-r</strong> (optimized, for ReHLDS/ReGameDLL). Either can host AMX Mod X, but a build meant for one engine dropped onto another can fail to open or behave oddly. If you run ReHLDS, prefer Metamod-r; on stock HLDS, classic Metamod is fine. Reinstall the flavour matching your stack rather than mixing binaries from different sources.</p><h2>4. Fix permissions and ownership</h2><p>If the <code>.so</code> is not readable/executable by the user the server runs as, the open fails. Set sane permissions:</p><pre>chmod 755 cstrike/addons/metamod/dlls/metamod_i386.so | |
| 15 | +chown -R csuser:csuser cstrike/addons</pre><p>A file uploaded by root and run by an unprivileged service account is a classic cause — the file is there but the process cannot read it.</p><h2>5. Confirm the game DLL Metamod loads is valid</h2><p>Metamod, once open, reads <code>addons/metamod/plugins.ini</code> and then loads the real game DLL. If the failing message is really about the game DLL and not Metamod itself, you are looking at a different problem — see <a href="/tutorials/fix-failed-to-load-server-dll">the cs.so load fix</a>. Read the error carefully: it names either <code>metamod</code> (this guide) or <code>cs.so</code> (that one).</p><h2>6. Rebuild liblist.gam from a known-good template</h2><p>If you have edited <code>liblist.gam</code> by hand and lost track of what changed, it is faster to restore a clean one than to keep guessing. A working Metamod-enabled <code>liblist.gam</code> keeps every stock line and changes only the game DLL pointers to Metamod's object:</p><pre>gamedll "addons\metamod\metamod.dll" | |
| 16 | +gamedll_linux "addons/metamod/dlls/metamod_i386.so" | |
| 17 | +</pre><p>Everything else — the game name, the fallback dir, the type — stays as the stock file shipped it. A common self-inflicted failure is deleting or corrupting an unrelated line in the file while editing the gamedll path, after which the engine cannot parse the file at all and skips Metamod silently. Keep a pristine copy of the stock <code>liblist.gam</code> so you can diff against it.</p><h2>Common errors</h2><ul><li><strong><code>cannot open shared object file</code></strong> — wrong path in <code>liblist.gam</code> or the file is missing. Match the line to the file on disk.</li><li><strong>File exists but still fails</strong> — missing 32-bit libs. Run <code>ldd</code> and install what shows <code>not found</code>.</li><li><strong><code>wrong ELF class: ELFCLASS64</code></strong> — a 64-bit Metamod build. Install the 32-bit (<code>_i386</code>) object.</li><li><strong>Permission denied</strong> — the service user cannot read the <code>.so</code>. Fix <code>chmod</code>/<code>chown</code>.</li><li><strong>Loads on HLDS, fails on ReHLDS</strong> — wrong flavour. Use Metamod-r for ReHLDS.</li></ul><h2>Verification</h2><p>Start the server and check Metamod reports itself:</p><pre>meta version | |
| 18 | +meta list</pre><p><code>meta version</code> printing a version string means Metamod opened successfully; <code>meta list</code> should then show your plugins including AMX Mod X. If AMX Mod X is present and running, the whole chain — engine to Metamod to AMXX — is healthy. From there, plugin-load problems are a separate layer; see <a href="/tutorials/fix-invalid-plugin-amxx">the Invalid Plugin fix</a> if individual plugins fail after Metamod is up.</p> |
fix-mysql-access-denied-amxx/en.md
A
| @@ -0,0 +1,14 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: MySQL 'Access denied for user' from an AMXX Plugin" | |
| 3 | +excerpt: "Reading the access-denied error an AMXX SQL plugin logs, where the credentials live in sql.cfg, the user@host grant trap, and getting a stats or ban plugin to authenticate." | |
| 4 | +category: "errors" | |
| 5 | +tags: "mysql, amxmodx" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>An AMX Mod X plugin that stores data in MySQL — a SQL stats system, a ban manager, a shop — fails to save anything, and the AMXX log shows an access-denied error. This is authentication, not connectivity: the game server reached MySQL fine, but MySQL rejected the username, password, or the host the connection came from. The fix is to line up the credentials the plugin sends with a MySQL account that is actually allowed to log in from the game server.</p><h2>1. Read the exact error</h2><p>Find it in <code>addons/amxmodx/logs/</code>. It looks like:</p><pre>Access denied for user 'csstats'@'gameserver.example.com' (using password: YES)</pre><p>Every part of that line is a clue. The user is <code>csstats</code>; the host MySQL saw the connection <em>from</em> is <code>gameserver.example.com</code>; and <code>using password: YES</code> means a password was sent (if it says <code>NO</code>, the plugin sent no password at all). MySQL grants are per user <strong>and</strong> per source host, so a grant for <code>'csstats'@'localhost'</code> does not authorize <code>'csstats'@'gameserver.example.com'</code>.</p><h2>2. Find where the plugin reads its credentials</h2><p>Most AMXX SQL plugins built on the <code>sqlx</code> module read one shared config:</p><pre>addons/amxmodx/configs/sql.cfg</pre><p>It sets the default host, user, password, and database via cvars:</p><pre>amx_sql_host "127.0.0.1" | |
| 10 | +amx_sql_user "csstats" | |
| 11 | +amx_sql_pass "yourpassword" | |
| 12 | +amx_sql_db "amxx" | |
| 13 | +amx_sql_type "mysql"</pre><p>Some plugins override these with their own cvars in their own config — a ban manager often has its own <code>amxbans_sql_*</code> or similar block. Check the plugin's config too, because a plugin-specific value wins over <code>sql.cfg</code> and is easy to overlook. Whatever the plugin actually sends must match a real MySQL account.</p><h2>3. Fix the credentials or the grant</h2><p>You have two levers — change what the plugin sends, or change what MySQL allows:</p><ul><li>If the username or password in <code>sql.cfg</code> is simply wrong, correct it. A trailing space or a copied smart-quote breaks the password silently.</li><li>If the credentials are right but the <strong>host</strong> is wrong in MySQL's eyes, grant access for the host the error names. For a game server connecting to a remote database, MySQL needs a grant for that source host (or a wildcard) — a <code>@'localhost'</code> grant will not cover a remote game server.</li></ul><p>The canonical grant, run in the MySQL client on the database host:</p><pre>GRANT ALL PRIVILEGES ON amxx.* TO 'csstats'@'gameserver.example.com' IDENTIFIED BY 'yourpassword'; | |
| 14 | +FLUSH PRIVILEGES;</pre><p>Match the user, the source host, and the password to exactly what the plugin sends. If the game server and MySQL are on the same box and the plugin uses <code>127.0.0.1</code>, grant for <code>'csstats'@'127.0.0.1'</code> or <code>'localhost'</code> accordingly — the two are not interchangeable in MySQL.</p><h2>4. Restart the plugin so it re-reads the config</h2><p>Changing <code>sql.cfg</code> or the plugin config does not re-authenticate a running plugin. Change the map (or restart the server) so the plugin reconnects with the corrected credentials. Watch the AMXX log on the next connect attempt.</p><h2>5. Watch the localhost versus 127.0.0.1 trap</h2><p>MySQL treats <code>localhost</code> and <code>127.0.0.1</code> as two different hosts, and this bites people constantly. A connection to <code>localhost</code> on Linux typically uses a Unix socket and matches a grant for <code>'user'@'localhost'</code>; a connection to <code>127.0.0.1</code> uses TCP and matches a grant for <code>'user'@'127.0.0.1'</code>. If the plugin's <code>amx_sql_host</code> is <code>127.0.0.1</code> but you only granted <code>@'localhost'</code>, MySQL denies it even though everything is on one box. Either grant for the exact host string the plugin sends, or align <code>amx_sql_host</code> to the host you granted. When in doubt, grant both and let the plugin use whichever it likes. This one detail resolves a large share of same-box access-denied errors that otherwise look inexplicable because "the password is obviously correct."</p><h2>Troubleshooting</h2><ul><li><strong><code>using password: NO</code></strong> — the plugin sent no password. The <code>amx_sql_pass</code> line is empty, misnamed, or in a config the plugin does not read.</li><li><strong>Works from <code>localhost</code>, fails from the game server</strong> — you granted <code>@'localhost'</code> but the connection comes from a different host. Grant for the host in the error.</li><li><strong>Right user, right host, still denied</strong> — the password does not match, or you added the grant without <code>FLUSH PRIVILEGES</code>. Reset the password and flush.</li><li><strong>Plugin ignores <code>sql.cfg</code></strong> — it uses its own cvars in its own config file, which override the shared one. Fix those.</li><li><strong>Credentials correct but connection refused (not denied)</strong> — that is a different error (errno 111), a networking problem, not authentication. See <a href="/tutorials/fix-mysql-cant-connect-111">can't connect to MySQL (111)</a>.</li></ul><h2>Verification</h2><p>Change the map to force a reconnect and watch <code>addons/amxmodx/logs/</code> — the access-denied line should be gone. Then exercise the plugin: for a stats plugin, get a kill and check that a row appears in the database; for a ban manager, issue a test ban and confirm the row is written. As an independent check, log in to MySQL from the game server host with the exact same user, host, and password the plugin uses and run a simple query — if that succeeds, the plugin will too, because it authenticates the same way.</p> |
fix-rtv-not-working/en.md
A
| @@ -0,0 +1,9 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: RTV (Rock the Vote) Not Triggering" | |
| 3 | +excerpt: "Why 'say rtv' does nothing, how the required-ratio and delay cvars gate the vote, the maps.ini and nextmap conflicts that break it, and how to confirm the vote actually fires." | |
| 4 | +category: "errors" | |
| 5 | +tags: "rtv, plugins" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>Players type <code>rtv</code> and nothing happens — no vote, no chat confirmation, no error. Rock the Vote is gated by several conditions on purpose (a minimum number of players must agree, a delay after map start must pass, a map list must exist), and any one of them unmet makes the command silently do nothing. That silence is what makes it hard to debug. Here is how to find which gate is closed.</p><h2>1. Confirm the plugin is loaded and named</h2><p>First rule out that RTV is not running at all. In the server console:</p><pre>amxx plugins</pre><p>Find the RTV plugin in the list; it must read <code>running</code>, not <code>bad load</code> or absent. If it is missing, its line is not in <a href="/tutorials/plugins-ini-load-order">plugins.ini</a>; if it is <code>bad load</code>, a required module is missing. No running RTV plugin means <code>rtv</code> is an unknown command and will always do nothing.</p><h2>2. Check the required ratio of voters</h2><p>RTV does not start a vote on one person's request — it needs a fraction of the server to type <code>rtv</code> first. That threshold is a cvar, commonly a ratio like <code>0.60</code> meaning 60% of players must ask. On a server with ten players that is six requests; with two players it is two. The relevant cvar in most RTV builds:</p><pre>rtv_ratio 0.60</pre><p>If one player types <code>rtv</code> and the ratio needs six, nothing visibly happens — the plugin is silently counting. Lower the ratio for a small server, or tell players the vote needs multiple requests. A ratio the current population can never reach is the number-one reason RTV "does not work" on a quiet server.</p><h2>3. Check the start delay</h2><p>Most RTV plugins block voting for the first few minutes of a map so people cannot skip a map the instant it loads. If the delay cvar is high, early <code>rtv</code> requests are ignored:</p><pre>rtv_delay 120</pre><p>With <code>rtv_delay 120</code>, requests in the first two minutes do nothing. Either wait out the delay or lower it. This is deliberate design, not a fault — but it looks identical to a broken plugin if you are testing right after a map change.</p><h2>4. Provide a map list to vote on</h2><p>RTV needs a pool of maps to offer. Depending on the build it reads from its own <code>maps.ini</code> (often in <code>addons/amxmodx/configs/</code>) or falls back to <a href="/tutorials/mapcycle-txt-setup">mapcycle.txt</a>. If that list is empty, missing, or lists maps the server does not have, the vote either never builds a menu or offers nothing valid. Confirm the map list file exists, is non-empty, and contains maps present in <code>cstrike/maps/</code>. A vote with no candidate maps cannot appear.</p><h2>5. Resolve nextmap and vote conflicts</h2><p>RTV usually pairs with a nextmap/vote plugin, and two plugins both trying to own the end-of-map vote can deadlock — each waiting for the other, so no vote fires. Run one coherent set: either the RTV plugin plus a matching nextmap plugin from the same suite, or a single combined plugin. Also confirm the end-of-map vote is scheduled off a non-zero <code>mp_timelimit</code>; a purely round-based flow (<code>mp_timelimit 0</code>) gives the timed vote nothing to hook onto, as explained in <a href="/tutorials/timelimit-maxrounds-flow">the map flow guide</a>. A well-integrated admin suite like the <a href="/plugins/csb-admin-menu">CSB Admin Menu</a> avoids the duplicate-vote deadlock.</p><h2>Troubleshooting</h2><ul><li><strong><code>rtv</code> is an unknown command</strong> — the plugin is not loaded. Check <code>amxx plugins</code> and plugins.ini.</li><li><strong>One player types rtv, nothing happens</strong> — the required ratio is not met; the plugin is counting silently. Lower <code>rtv_ratio</code> or get more requests.</li><li><strong>rtv ignored early in the map</strong> — <code>rtv_delay</code> has not elapsed. Wait or lower it.</li><li><strong>Vote menu is empty or never appears</strong> — the map list is missing or lists absent maps. Fix <code>maps.ini</code>/mapcycle.</li><li><strong>Vote starts but never changes map</strong> — a nextmap plugin conflict, or the winning map is not installed. Run one coherent vote set.</li><li><strong>End-of-map vote never fires</strong> — <code>mp_timelimit 0</code>. Keep a non-zero time limit as the vote's schedule.</li></ul><h2>Verification</h2><p>On a test server, set <code>rtv_ratio</code> low and <code>rtv_delay</code> to a few seconds, load a map, wait out the delay, and type <code>rtv</code>. The plugin should acknowledge the request in chat and, once the ratio is met, open the map vote menu. Cast votes and confirm the server changes to the winning map. If the request is acknowledged and the menu appears, RTV is functioning and your live problem was one of the gates — usually the ratio being unreachable at your real population. Restore sensible values (a reasonable ratio and a short protective delay) once it works, and advertise the command in your <a href="/tutorials/motd-txt-html-guide">MOTD</a> so players know it needs several requests.</p> |
fix-sv-downloadurl-ignored/en.md
A
| @@ -0,0 +1,10 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fix: sv_downloadurl Ignored by Clients" | |
| 3 | +excerpt: "Why clients ignore your sv_downloadurl and fall back to slow downloads or missing content, the cstrike path trap, the bzip2 layout, and the exact checks that make FastDL fire." | |
| 4 | +category: "errors" | |
| 5 | +tags: "fastdl, downloads" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>You set <code>sv_downloadurl</code>, but clients still download at a crawl over the in-game channel, or join with missing textures as if the URL were not there. FastDL being "ignored" almost always comes down to a small number of concrete mistakes: the master download switch is off, the URL path is wrong, the files are not actually on the web host, or the client opted out. This is the checklist that gets HTTP downloads firing.</p><h2>1. Confirm the prerequisites are on</h2><p><code>sv_downloadurl</code> does nothing unless downloads are allowed in the first place:</p><pre>sv_allowdownload 1 | |
| 10 | +sv_downloadurl "http://dl.example.com/cs"</pre><p>With <code>sv_allowdownload 0</code>, no download happens at all and the URL is irrelevant. With allow on but the URL empty or unreachable, the server falls back to the slow UDP transfer — which people mistake for "the URL is ignored". So first prove <code>sv_allowdownload</code> is <code>1</code> and the URL cvar is actually set by reading it back:</p><pre>sv_downloadurl</pre><p>Type it with no argument in the console; it must echo your URL, not an empty string. If it is empty, your <a href="/tutorials/setup-server-cfg-cs16">server.cfg</a> did not set it, or something reset it later in the exec order.</p><h2>2. Get the cstrike path right — the number-one cause</h2><p>The engine appends the game-relative path, <strong>including the <code>cstrike/</code> folder</strong>, to your base URL. So for a map file the client requests:</p><pre>http://dl.example.com/cs/cstrike/maps/de_rats.bsp</pre><p>Your web root must therefore contain a <code>cstrike/</code> directory that mirrors the server's layout. The two failure modes are equal and opposite: <strong>missing</strong> the <code>cstrike/</code> level (files at <code>/cs/maps/</code> instead of <code>/cs/cstrike/maps/</code>) or <strong>doubling</strong> it (you set the URL to <code>.../cs/cstrike</code> and the engine appends another <code>cstrike</code>). Either way every request 404s and the client silently falls back. Open the exact URL above in a browser — if it does not download the file, no client can either.</p><h2>3. Put the files where the URL points</h2><p>FastDL only serves files that exist on the web host. Uploading a new map to the game server but forgetting the mirror is the classic "works for me, missing for players" bug. Every custom file the map references — the <code>.bsp</code>, models, sounds, sprites — must be copied to the mirror under the matching path. Keep the mirror in sync with your content and your <a href="/tutorials/mapcycle-txt-setup">map rotation</a>. A single missing model on the mirror is a 404 for that file only, which reads as "most content works but this one thing is missing".</p><h2>4. Handle bzip2 compression correctly</h2><p>FastDL commonly serves <strong>bzip2-compressed</strong> copies to cut transfer size. The client requests the <code>.bz2</code> version first and falls back to the raw file if it is absent. If you compressed the files, the compressed name must be exactly the original plus <code>.bz2</code>:</p><pre>cstrike/maps/de_rats.bsp.bz2</pre><p>A common mistake is compressing into a wrong name or leaving only the <code>.bz2</code> with a broken original, so neither the compressed nor the raw request resolves. Serve both, or at minimum the correctly-named <code>.bz2</code>. The <a href="/tutorials/fastdl-setup-guide">FastDL setup guide</a> covers the Nginx/Apache config and how to generate the <code>.bz2</code> set.</p><h2>5. Rule out the client and the MIME type</h2><p>Two last traps:</p><ul><li><strong>Client opted out</strong> — a player with <code>cl_allowdownload 0</code> ignores your URL and every other download. You cannot override it; only they can set it back to <code>1</code>.</li><li><strong>Web server MIME/permissions</strong> — if the host returns the file with the wrong content type, or the files are not world-readable, the client's fetch fails and it falls back. Ensure the web root files are readable and the server serves <code>.bsp</code>/<code>.bz2</code> as plain downloads, not as some interpreted type.</li></ul><h2>Troubleshooting</h2><ul><li><strong>Downloads still slow</strong> — the URL is empty/unreachable, so it fell back to UDP. Read <code>sv_downloadurl</code> back and open the file URL in a browser.</li><li><strong>Every file 404s over HTTP</strong> — the <code>cstrike/</code> level is missing or doubled. The request must resolve at <code><url>/cstrike/maps/...</code>.</li><li><strong>Most content works, one file missing</strong> — that single file is not on the mirror. Copy it up.</li><li><strong>Only one player misses everything</strong> — that player set <code>cl_allowdownload 0</code>.</li><li><strong>Browser downloads the file but the client still fails</strong> — wrong MIME type or a missing <code>.bz2</code> the client expected. Serve a correctly-named <code>.bz2</code> or the raw file with a plain download type.</li></ul><h2>Verification</h2><p>Paste the exact expected URL — <code>http://dl.example.com/cs/cstrike/maps/<yourmap>.bsp</code> — into a browser and confirm it downloads. Then from a clean test client (delete the map locally first), connect and watch <code>net_graph</code> or the loading bar: a fast HTTP pull means FastDL fired; a slow crawl means it fell back and one of the checks above is still wrong. Once one map downloads fast over HTTP, the pipeline is correct and only content-sync discipline keeps it that way. For how these cvars relate to the client-side vetoes, see <a href="/tutorials/fix-stuck-verifying-resources">stuck on verifying resources</a>.</p> |
gametracker-claim-server/en.md
A
| @@ -0,0 +1,9 @@ | ||
| 1 | +--- | |
| 2 | +title: "Claiming and Optimizing Your GameTracker Page" | |
| 3 | +excerpt: "How to add or claim your CS 1.6 server on GameTracker, why claiming matters, how to brand the page and use the banner, and how to keep the listing accurate after an IP change." | |
| 4 | +category: "hosting" | |
| 5 | +tags: "gametracker, listing" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>GameTracker is the listing that most CS 1.6 players will actually see before they join, and an unclaimed, unbranded page is a wasted asset. Claiming it lets you control the description, add your Discord and website, display your rules, and use the live banner everywhere you advertise. This walks through adding the server, claiming it, optimizing the page, and keeping it accurate.</p><h2>1. Make sure GameTracker can see the server</h2><p>GameTracker finds servers through the master list and by querying <code>IP:port</code>. Before adding anything, confirm your server is publicly queryable — in the in-game browser and answering A2S queries. If it is not, fix that first with <a href="/tutorials/get-server-in-master-list">getting into the master list</a>; GameTracker will only track a server it can reach.</p><h2>2. Add the server by IP and port</h2><p>On GameTracker, use the add-server function and enter your server as <code>IP:port</code> for the Counter-Strike 1.6 game. GameTracker begins querying it and, once it gets responses, creates a page that records map history, player counts, and an activity graph. A brand-new page starts with no rank and no history — that accumulates as the server is tracked over days and weeks.</p><h2>3. Claim the page so you control it</h2><p>An added server is public but unowned. <strong>Claiming</strong> ties the page to your GameTracker account and unlocks editing. You claim from the server's page while logged in; GameTracker verifies ownership before granting control. Once claimed you can edit the description, website link, and connect info, and manage the associated banner — none of which an unclaimed page allows. Claim it even if you added it yourself; adding and claiming are separate steps.</p><h2>4. Optimize what players see</h2><p>Fill the editable fields with the things that convert a browser into a joiner:</p><ul><li><strong>Description</strong> — state the mod, the vibe, and any signature feature in one line. Match it to your in-game <code>hostname</code> so the two reinforce each other.</li><li><strong>Website / Discord</strong> — link your community home; this is a free funnel into your <a href="/tutorials/discord-server-integration">Discord</a>.</li><li><strong>Connect info</strong> — publish a clean <code>connect</code> address, ideally a <a href="/tutorials/domain-name-for-server">domain</a> so it survives an IP change.</li></ul><p>Then take the GameTracker <strong>banner</strong> — the live image showing current players and map — and paste it into your <a href="/tutorials/motd-txt-html-guide">MOTD</a>, forum signature, and Discord. Every place the banner appears is a live advert that updates itself.</p><h2>5. Drive the rank you are advertising</h2><p>The page is only as attractive as the activity it shows, and GameTracker ranks on sustained real activity and votes. Understand the mechanics before you try to climb: see <a href="/tutorials/gametracker-ranking-explained">how GameTracker ranking works</a>. The short version — genuine players over consistent hours raise the rank; there is no shortcut, and faking the count only risks a flag (<a href="/tutorials/fake-players-bot-padding">fake players and bot padding</a>).</p><h2>6. Keep the listing accurate after changes</h2><p>The most common failure is a stale page. If your server changes IP or port, the tracked address stops responding and the page shows offline — you must update the claimed listing's connect address, or GameTracker eventually treats it as dead. This is a strong argument for advertising a <a href="/tutorials/domain-name-for-server">domain</a> and keeping the DNS current, so the human-facing connect string never changes even when the underlying IP does. GameTracker still queries by IP, so update the listing itself when the IP moves.</p><h2>Common errors</h2><ul><li><strong>Page stuck "offline"</strong> — GameTracker cannot query the address. Confirm the server is in the browser and the listed port is exactly right; a non-default port must be specified.</li><li><strong>Cannot edit the page</strong> — you added it but never claimed it. Claim it from the page while logged in.</li><li><strong>Rank is zero on a busy server</strong> — the page is new; ranking needs tracked history to build. Give it time with real activity.</li><li><strong>Banner shows old data</strong> — the page tracks a stale IP. Update the connect address on the claimed listing.</li><li><strong>Duplicate pages for one server</strong> — it was re-added after an IP change. Claim and maintain the current one; report the stale duplicate.</li></ul><h2>Verification</h2><p>After claiming and editing, load your GameTracker page in a private browser window (logged out) and confirm the public view shows your description, links, and a live, correct player count and map. Change the map in-game and confirm the page and banner update on the next query cycle. Finally, click your own banner from the MOTD to confirm it links back to the page — that round trip is the funnel doing its job. For the wider listing strategy, see <a href="/tutorials/cs16-toplists-worth-joining">toplists worth joining</a>.</p> |
track-player-counts-peak-hours/en.md
A
| @@ -0,0 +1,10 @@ | ||
| 1 | +--- | |
| 2 | +title: "Tracking Player Counts and Peak Hours on Your CS 1.6 Server" | |
| 3 | +excerpt: "How to measure when your CS 1.6 server is actually busy: reading player counts from status, logging counts on a cron, using tracker history, and turning the data into decisions." | |
| 4 | +category: "hosting" | |
| 5 | +tags: "analytics, growth" | |
| 6 | +image: "" | |
| 7 | +read_time: "8 min read" | |
| 8 | +--- | |
| 9 | +<p>You cannot grow a server you are not measuring. "It feels busy in the evening" is a guess; a graph of players by hour is a decision. Knowing your real peak hours tells you when to be online to seed, when to schedule restarts so they miss the crowd, and whether a change actually helped. CS 1.6 gives you everything you need to build this yourself with no special software. This covers reading the count live, logging it over time, and acting on it.</p><h2>1. Read the count live</h2><p>The server always knows its current population. From the console or over <a href="/tutorials/rcon-commands-list">RCON</a>:</p><pre>status</pre><p>The <code>status</code> output lists every connected player with their name, SteamID, ping and IP, and the header shows the player count and map. For a scriptable single number, the server responds to the standard A2S info query on its UDP port (default <strong>27015</strong>) with the current and max player counts — the same data a <a href="/tutorials/server-banners-signatures">banner</a> reads. Either way you have the instantaneous count; the value is in recording it over time.</p><h2>2. Log the count on a schedule</h2><p>To see peaks you need a time series. The simplest reliable method is a cron job on the server host that samples the count every few minutes and appends it with a timestamp to a log file. Query the count with an A2S tool or parse it from an <a href="/tutorials/rcon-from-linux-cli">RCON <code>status</code> call</a>, then append:</p><pre># /etc/cron.d/cs-playercount — sample every 5 minutes | |
| 10 | +*/5 * * * * csuser /home/cs/count.sh >> /home/cs/players.log 2>&1</pre><p>Each line becomes <code>timestamp count</code>. After a week you have 2000 samples — enough to see the shape of every day. This is the same cron mechanism used for <a href="/tutorials/cron-restart-cs16-server">scheduled restarts</a>; you are just reading state instead of changing it.</p><h2>3. Turn the log into peak hours</h2><p>Group the samples by hour of day and average them. The hours with the highest average count are your peak — that is when to seed the server yourself, when to run events, and the window a new player is most likely to find it populated. The hours near zero are when a restart or map-heavy maintenance costs you nothing. Look for the <em>daily</em> pattern (evenings almost always dominate) and the <em>weekly</em> one (weekends usually higher). A week of data makes both obvious without any fancy tooling — even eyeballing the log grouped by hour tells the story.</p><h2>4. Use a tracker for history you did not log yourself</h2><p>If you did not start logging early, a public server tracker that you registered your server on (the same one that draws your <a href="/tutorials/server-banners-signatures">banner</a>) keeps a player-count history and often a peak-hours chart for free, because it has been querying your server on a schedule anyway. It is coarser than your own log but requires zero setup and covers the past. Register the server and let it accumulate history in the background as a backstop to your own sampling.</p><h2>5. Act on what the data shows</h2><p>The whole point is decisions. Peak at 8–11pm local? Be online at 8 to seed, and never schedule an automatic restart inside that window. Weekend spike? Run your events Saturday night. Made a change — new mode, FastDL fix, a marketing push — and want to know if it worked? Compare the week before to the week after on the same hours. Without the log you are guessing; with it you can prove a change moved the count.</p><h2>Common mistakes</h2><ul><li><strong>Count is always zero in the log</strong> — the query hit the wrong port or <code>sv_lan 1</code> blocks it. Confirm A2S responds on 27015; see <a href="/tutorials/fix-a2s-info-no-response">A2S no response</a>.</li><li><strong>Cron job never runs</strong> — wrong user or a non-executable script. Check the cron log and that <code>count.sh</code> has execute permission.</li><li><strong>Peak looks random</strong> — too few samples or you mixed time zones. Sample for a full week and normalise to one local time zone.</li><li><strong>Bots inflate the count</strong> — if you run <a href="/tutorials/install-yapb-bots">bots</a>, they may be counted; filter them out or you will misread real demand.</li><li><strong>Restart lands in prime time</strong> — you scheduled it before you had the data. Move it into a trough once you know where the trough is.</li></ul><h2>Verification</h2><p>Run <code>status</code> and confirm the header count matches the number of players you can see in-game right now — that proves your source of truth. Let the cron logger run for a full week, then group the log by hour and confirm the shape matches your gut (a clear evening hump). If the graph and your intuition disagree, trust the graph and adjust your seeding time and restart schedule to fit it. A server run on real peak-hour data fills faster than one run on hunches.</p> | |
| 0 | 11 |









