<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title><![CDATA[open.mp forum - Plugins]]></title>
		<link>https://forum.open.mp/</link>
		<description><![CDATA[open.mp forum - https://forum.open.mp]]></description>
		<pubDate>Mon, 19 Oct 2026 01:17:40 +0000</pubDate>
		<generator>MyBB</generator>
		<item>
			<title><![CDATA[omp-postgresql — Asynchronous PostgreSQL Component for open.mp (v0.1.0 Experimental)]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6559</link>
			<pubDate>Thu, 15 Oct 2026 21:19:35 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8236">itskoleban</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6559</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">omp-postgresql - Asynchronous PostgreSQL Component for open.mp</span><br />
<span style="font-style: italic;" class="mycode_i">Version 0.1.0 Experimental</span><br />
<br />
Hello everyone!<br />
<br />
I'd like to introduce omp-postgresql, an open-source PostgreSQL database component built specifically for open.mp. It is written in C++20, uses libpq, and provides an asynchronous, parameterized Pawn API through the native open.mp Component SDK.<br />
<br />
The project focuses on predictable concurrency, safe resource management, explicit error handling, and maintainability.<br />
<br />
The first experimental release includes downloadable Windows x86 and Linux x86 packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Important:</span> This is an experimental release for evaluation and testing. It is not yet recommended for production environments.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features</span><br />
<br />
- Native open.mp component (not a legacy SA-MP plugin).<br />
- Asynchronous database operations with Pawn callbacks dispatched on the server's main thread.<br />
- libpq service-profile connections and configurable connection pools.<br />
- FIFO execution lanes with dedicated session workers.<br />
- Prepared statements and typed, bound parameters.<br />
- Homogeneous executions and heterogeneous batches.<br />
- Transactions, configurable isolation, rollback, and savepoints.<br />
- PostgreSQL types including BIGINT, NUMERIC, UUID, JSONB, BYTEA, and arrays.<br />
- SQL loading from files with path confinement and validation.<br />
- Bounded result streaming with backpressure.<br />
- Cancellation, timeouts, reconnection, and explicit job outcomes.<br />
- Resource ownership tied to Pawn VM lifetimes.<br />
- Job diagnostics, result metadata, and connection statistics.<br />
<br />
A key design goal is to handle failures safely. The API distinguishes confirmed, failed, cancelled, and uncertain operations. Writes with uncertain outcomes are not automatically replayed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Quick Example</span><br />
<br />
This example connects using a libpq service profile, submits a parameterized query, and reads the result asynchronously:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;omp-postgresql&gt;<br />
<br />
new g_Database;<br />
<br />
public OnGameModeInit()<br />
{<br />
    g_Database = pg_connect("game", 2);<br />
<br />
    if (!g_Database)<br />
    {<br />
        print("[PostgreSQL] Connection initialization failed.");<br />
    }<br />
<br />
    return 1;<br />
}<br />
<br />
public OnPgConnection(connection, connection_state)<br />
{<br />
    if (connection != g_Database || connection_state != PG_CONNECTED)<br />
    {<br />
        return 1;<br />
    }<br />
<br />
    new statement = pg_prepare(<br />
        connection,<br />
        "SELECT &#36;1::integer AS answer",<br />
        1<br />
    );<br />
<br />
    if (!statement)<br />
    {<br />
        return 1;<br />
    }<br />
<br />
    new job;<br />
    if (pg_bind_int(statement, 1, 42) &amp;&amp; pg_add(statement))<br />
    {<br />
        job = pg_execute(statement);<br />
    }<br />
<br />
    pg_statement_close(statement);<br />
<br />
    if (!job)<br />
    {<br />
        print("[PostgreSQL] Query submission failed.");<br />
    }<br />
<br />
    return 1;<br />
}<br />
<br />
public OnPgComplete(job, context)<br />
{<br />
    new answer;<br />
<br />
    if (pg_job_status(job) == PG_CONFIRMED &amp;&amp;<br />
        pg_result_rows(job) &gt; 0 &amp;&amp;<br />
        pg_result_int(job, 0, 0, 0, answer))<br />
    {<br />
        printf("[PostgreSQL] Answer: %d", answer);<br />
    }<br />
<br />
    pg_job_close(job);<br />
    return 1;<br />
}<br />
<br />
public OnGameModeExit()<br />
{<br />
    if (g_Database)<br />
    {<br />
        pg_connection_close(g_Database);<br />
    }<br />
<br />
    return 1;<br />
}</code></div></div><br />
The "game" connection profile must be configured through libpq before starting the server. Credentials do not need to be embedded in the gamemode. See the installation guide and the complete runnable examples in the repository.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Testing and Validation</span><br />
<br />
The project has automated validation covering:<br />
<br />
- Unit tests and PostgreSQL integration tests.<br />
- Windows and Linux x86 packages tested with official open.mp servers.<br />
- Engine tests on Windows and Linux x64.<br />
- AddressSanitizer and UndefinedBehaviorSanitizer on supported Linux builds.<br />
- Connection failures, cancellation, reconnection, and transaction recovery.<br />
- Pawn VM unload and callback lifecycle scenarios.<br />
- Concurrent workloads and resource teardown stress tests.<br />
- Package architecture, dependencies, checksums, and installation checks.<br />
<br />
The experimental release passed six CI jobs. The default stress suite covers 4,640 SQL operations, and a separate local run completed 11,600 operations. These are test results, not production guarantees or general throughput benchmarks.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Installation</span><br />
<br />
1. Download the Windows x86 or Linux x86 package from GitHub Releases.<br />
2. Place the component in the open.mp server's components/ directory.<br />
3. Install omp-postgresql.inc in the Pawn compiler's include directory.<br />
4. Configure PGSERVICEFILE and the required libpq authentication settings.<br />
5. Review the installation guide for platform-specific runtime dependencies.<br />
<br />
The published packages are not fully self-contained. Compatible system runtime libraries may still be required, especially for x86 deployments.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Current Status and Roadmap</span><br />
<br />
The project is under active development. Before the first Release Candidate, priorities include longer-duration failure and stress tests, malformed AMX input testing, additional performance measurements, and an independent audit.<br />
<br />
The initial scope intentionally excludes an ORM, synchronous Pawn queries, COPY transfers, LISTEN/NOTIFY reception, and PostgreSQL pipeline mode.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Downloads and Documentation</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">GitHub repository:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgresql</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Experimental release:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/releases/tag/v0.1.0-experimental.b00fe2f23787" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...0fe2f23787</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Pawn API documentation:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/api.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...ocs/api.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Installation guide:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/installation.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...llation.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Validation and known limitations:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/validation.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...idation.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Bug reports and suggestions:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/issues" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgresql/issues</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">License:</span> MIT<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Feedback Welcome</span><br />
<br />
This is the first public experimental release, and feedback from the open.mp community would be greatly appreciated.<br />
<br />
I'm particularly interested in feedback about the Pawn API, installation, compatibility, concurrency behavior, and edge cases. If you encounter a problem, please open a GitHub issue with reproduction steps, your operating system, and relevant logs.<br />
<br />
Contributions and pull requests are welcome.<br />
<br />
Thanks for taking a look!]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">omp-postgresql - Asynchronous PostgreSQL Component for open.mp</span><br />
<span style="font-style: italic;" class="mycode_i">Version 0.1.0 Experimental</span><br />
<br />
Hello everyone!<br />
<br />
I'd like to introduce omp-postgresql, an open-source PostgreSQL database component built specifically for open.mp. It is written in C++20, uses libpq, and provides an asynchronous, parameterized Pawn API through the native open.mp Component SDK.<br />
<br />
The project focuses on predictable concurrency, safe resource management, explicit error handling, and maintainability.<br />
<br />
The first experimental release includes downloadable Windows x86 and Linux x86 packages.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Important:</span> This is an experimental release for evaluation and testing. It is not yet recommended for production environments.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features</span><br />
<br />
- Native open.mp component (not a legacy SA-MP plugin).<br />
- Asynchronous database operations with Pawn callbacks dispatched on the server's main thread.<br />
- libpq service-profile connections and configurable connection pools.<br />
- FIFO execution lanes with dedicated session workers.<br />
- Prepared statements and typed, bound parameters.<br />
- Homogeneous executions and heterogeneous batches.<br />
- Transactions, configurable isolation, rollback, and savepoints.<br />
- PostgreSQL types including BIGINT, NUMERIC, UUID, JSONB, BYTEA, and arrays.<br />
- SQL loading from files with path confinement and validation.<br />
- Bounded result streaming with backpressure.<br />
- Cancellation, timeouts, reconnection, and explicit job outcomes.<br />
- Resource ownership tied to Pawn VM lifetimes.<br />
- Job diagnostics, result metadata, and connection statistics.<br />
<br />
A key design goal is to handle failures safely. The API distinguishes confirmed, failed, cancelled, and uncertain operations. Writes with uncertain outcomes are not automatically replayed.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Quick Example</span><br />
<br />
This example connects using a libpq service profile, submits a parameterized query, and reads the result asynchronously:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;omp-postgresql&gt;<br />
<br />
new g_Database;<br />
<br />
public OnGameModeInit()<br />
{<br />
    g_Database = pg_connect("game", 2);<br />
<br />
    if (!g_Database)<br />
    {<br />
        print("[PostgreSQL] Connection initialization failed.");<br />
    }<br />
<br />
    return 1;<br />
}<br />
<br />
public OnPgConnection(connection, connection_state)<br />
{<br />
    if (connection != g_Database || connection_state != PG_CONNECTED)<br />
    {<br />
        return 1;<br />
    }<br />
<br />
    new statement = pg_prepare(<br />
        connection,<br />
        "SELECT &#36;1::integer AS answer",<br />
        1<br />
    );<br />
<br />
    if (!statement)<br />
    {<br />
        return 1;<br />
    }<br />
<br />
    new job;<br />
    if (pg_bind_int(statement, 1, 42) &amp;&amp; pg_add(statement))<br />
    {<br />
        job = pg_execute(statement);<br />
    }<br />
<br />
    pg_statement_close(statement);<br />
<br />
    if (!job)<br />
    {<br />
        print("[PostgreSQL] Query submission failed.");<br />
    }<br />
<br />
    return 1;<br />
}<br />
<br />
public OnPgComplete(job, context)<br />
{<br />
    new answer;<br />
<br />
    if (pg_job_status(job) == PG_CONFIRMED &amp;&amp;<br />
        pg_result_rows(job) &gt; 0 &amp;&amp;<br />
        pg_result_int(job, 0, 0, 0, answer))<br />
    {<br />
        printf("[PostgreSQL] Answer: %d", answer);<br />
    }<br />
<br />
    pg_job_close(job);<br />
    return 1;<br />
}<br />
<br />
public OnGameModeExit()<br />
{<br />
    if (g_Database)<br />
    {<br />
        pg_connection_close(g_Database);<br />
    }<br />
<br />
    return 1;<br />
}</code></div></div><br />
The "game" connection profile must be configured through libpq before starting the server. Credentials do not need to be embedded in the gamemode. See the installation guide and the complete runnable examples in the repository.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Testing and Validation</span><br />
<br />
The project has automated validation covering:<br />
<br />
- Unit tests and PostgreSQL integration tests.<br />
- Windows and Linux x86 packages tested with official open.mp servers.<br />
- Engine tests on Windows and Linux x64.<br />
- AddressSanitizer and UndefinedBehaviorSanitizer on supported Linux builds.<br />
- Connection failures, cancellation, reconnection, and transaction recovery.<br />
- Pawn VM unload and callback lifecycle scenarios.<br />
- Concurrent workloads and resource teardown stress tests.<br />
- Package architecture, dependencies, checksums, and installation checks.<br />
<br />
The experimental release passed six CI jobs. The default stress suite covers 4,640 SQL operations, and a separate local run completed 11,600 operations. These are test results, not production guarantees or general throughput benchmarks.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Installation</span><br />
<br />
1. Download the Windows x86 or Linux x86 package from GitHub Releases.<br />
2. Place the component in the open.mp server's components/ directory.<br />
3. Install omp-postgresql.inc in the Pawn compiler's include directory.<br />
4. Configure PGSERVICEFILE and the required libpq authentication settings.<br />
5. Review the installation guide for platform-specific runtime dependencies.<br />
<br />
The published packages are not fully self-contained. Compatible system runtime libraries may still be required, especially for x86 deployments.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Current Status and Roadmap</span><br />
<br />
The project is under active development. Before the first Release Candidate, priorities include longer-duration failure and stress tests, malformed AMX input testing, additional performance measurements, and an independent audit.<br />
<br />
The initial scope intentionally excludes an ORM, synchronous Pawn queries, COPY transfers, LISTEN/NOTIFY reception, and PostgreSQL pipeline mode.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Downloads and Documentation</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">GitHub repository:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgresql</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Experimental release:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/releases/tag/v0.1.0-experimental.b00fe2f23787" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...0fe2f23787</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Pawn API documentation:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/api.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...ocs/api.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Installation guide:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/installation.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...llation.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Validation and known limitations:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/blob/master/docs/validation.md" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgr...idation.md</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Bug reports and suggestions:</span><br />
<a href="https://github.com/itskoleban/omp-postgresql/issues" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/omp-postgresql/issues</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">License:</span> MIT<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Feedback Welcome</span><br />
<br />
This is the first public experimental release, and feedback from the open.mp community would be greatly appreciated.<br />
<br />
I'm particularly interested in feedback about the Pawn API, installation, compatibility, concurrency behavior, and edge cases. If you encounter a problem, please open a GitHub issue with reproduction steps, your operating system, and relevant logs.<br />
<br />
Contributions and pull requests are welcome.<br />
<br />
Thanks for taking a look!]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[mysql_samp 1.4.0 — notas de versão para SA-MP e open.mp]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6558</link>
			<pubDate>Sun, 11 Oct 2026 15:43:52 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=7983">NullSablex</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6558</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">🦀 mysql_samp 1.4.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">MySQL para SA-MP e open.mp — o mesmo binário nos dois</span><br />
<span style="font-style: italic;" class="mycode_i">MySQL for SA-MP and open.mp — the same binary on both</span></span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> · <a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Plugin MySQL em Rust, <span style="font-weight: bold;" class="mycode_b">79 natives</span>, sem dependência para instalar. Esta versão, em três linhas:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Uma mudança de comportamento:</span> o <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> entregava seus cinco argumentos na ordem invertida, desde a 1.0.0. Se você trata esse forward, confira o handler.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Uma correção de estabilidade:</span> uma rajada de queries num único tick podia abortar o servidor e travar a fila de callbacks permanentemente.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Uma novidade:</span> <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> faz uma chamada específica esperar o banco, sem callback.<br />
</li>
</ul>
<br />
🇧🇷 Nenhuma native foi removida ou renomeada e nenhuma assinatura mudou: gamemode de 1.3.0 compila como está.<br />
<br />
🇬🇧 MySQL plugin in Rust, <span style="font-weight: bold;" class="mycode_b">79 natives</span>, nothing to install. One behaviour change (<span style="font-weight: bold;" class="mycode_b">OnQueryError</span> delivered its five arguments reversed since 1.0.0 — check your handler), one stability fix (a burst of queries in one tick could abort the server and stall the callback queue for good), one new feature (<span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> makes a single call wait on the database, with no callback). Nothing removed, renamed or re-signed: 1.3.0 gamemodes compile unchanged.</blockquote>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚠️ Mudança de comportamento: OnQueryError</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Behaviour change: OnQueryError</span></span><br />
<br />
🇧🇷 O forward é declarado assim:<br />
🇬🇧 The forward is declared as:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>forward OnQueryError(errorid, error[], callback[], query[], connId);</code></div></div><br />
🇧🇷 Até a 1.3.0 o gamemode recebia os campos ao contrário: o id da conexão chegava como <span style="font-weight: bold;" class="mycode_b">errorid</span>, o texto da query como <span style="font-weight: bold;" class="mycode_b">error</span>, a mensagem de erro como <span style="font-weight: bold;" class="mycode_b">query</span> e o código do MySQL como <span style="font-weight: bold;" class="mycode_b">connId</span>.<br />
<br />
🇧🇷 A causa estava no despacho. O <span style="font-weight: bold;" class="mycode_b">exec_public!</span> empilha os argumentos ao contrário — é a convenção da pilha do AMX — logo a ordem escrita na chamada já é a ordem que o <span style="font-weight: bold;" class="mycode_b">public</span> recebe. O código os listava de trás para frente, como se tivesse de inverter à mão.<br />
<br />
🇧🇷 A inversão era consistente, então um handler podia parecer funcionar há bastante tempo lendo o id da conexão como código de erro. <span style="font-weight: bold;" class="mycode_b">Presente desde a 1.0.0</span> — se você trata esse forward, é o primeiro lugar a olhar depois de atualizar.<br />
<br />
🇬🇧 Up to 1.3.0 the fields arrived reversed: connection id as <span style="font-weight: bold;" class="mycode_b">errorid</span>, query text as <span style="font-weight: bold;" class="mycode_b">error</span>, error message as <span style="font-weight: bold;" class="mycode_b">query</span>, MySQL code as <span style="font-weight: bold;" class="mycode_b">connId</span>. <span style="font-weight: bold;" class="mycode_b">exec_public!</span> pushes arguments in reverse (the AMX stack convention), so the order written at the call site is already the order the <span style="font-weight: bold;" class="mycode_b">public</span> receives; the code listed them backwards as if it had to invert them by hand. Consistent enough that a handler could appear to work for a long time. Present since 1.0.0.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ao atualizar / 🇬🇧 And when updating</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Troque o binário e os includes.</span> O release traz <span style="font-weight: bold;" class="mycode_b">.so</span>, <span style="font-weight: bold;" class="mycode_b">.dll</span>, <span style="font-weight: bold;" class="mycode_b">mysql_samp.inc</span> e <span style="font-weight: bold;" class="mycode_b">mysql_samp_omp.inc</span>. Sem o include novo não existem <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> nem as natives novas — e include velho com binário novo é o erro mais comum numa atualização. O <span style="font-weight: bold;" class="mycode_b">MYSQL_SAMP_VERSION</span> do include serve justamente para comparar na subida / 🇬🇧 replace the binary and the includes; <span style="font-weight: bold;" class="mycode_b">MYSQL_SAMP_VERSION</span> exists to catch a stale one<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Binário de 32 bits</span>, como o servidor. SA-MP em <span style="font-weight: bold;" class="mycode_b">plugins/</span>; open.mp nativo em <span style="font-weight: bold;" class="mycode_b">components/</span>, sem mexer no <span style="font-weight: bold;" class="mycode_b">config.json</span> / 🇬🇧 <span style="font-weight: bold;" class="mycode_b">32-bit</span>, like the server<br />
</li>
<li>🇧🇷 Fora o handler do <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, nada mais a fazer / 🇬🇧 apart from the <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> handler, nothing else to do<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🐛 Correções</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Fixed</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Uma rajada de queries matava o servidor — e podia travar a fila para sempre</span><br />
🇧🇷 Submeter 2000 queries num tick abortava o processo com <span style="font-weight: bold;" class="mycode_b">memory allocation of 4194304 bytes failed</span>. A causa é específica desta arquitetura: uma thread por query, com a pilha padrão de 8 MiB, dentro de um servidor <span style="font-weight: bold;" class="mycode_b">32 bits</span>. Algumas centenas de workers vivos esgotam o espaço de endereçamento, e a próxima alocação que falha leva o processo junto. Foi achado por teste de força bruta, não por relato.<br />
<br />
🇧🇷 Eram dois problemas, os dois corrigidos:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Pilha de 256 KiB por worker</span> — um worker abre conexão, roda um statement e monta um cache; não recursa. O teto medido saiu de cerca de 1700 para mais de 9000 workers<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Thread recusada pelo sistema não é mais pânico</span> — e esta parte era pior que o crash: a submissão se perdia junto com o número de sequência, de modo que toda callback ordenada atrás dela esperaria para sempre. Agora é um resultado com erro comum: a sequência é contabilizada, o <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> dispara e a native devolve <span style="font-weight: bold;" class="mycode_b">false</span><br />
</li>
</ul>
🇧🇷 Medido depois da correção: 9000 queries num tick, 9000 callbacks entregues, ordem intacta, nada preso no caminho.<br />
🇬🇧 2000 queries in one tick aborted the process — one thread per query, 8 MiB default stack, <span style="font-weight: bold;" class="mycode_b">32-bit</span> server. Workers now get a 256 KiB stack (ceiling moved from ~1700 to past 9000), and a thread the OS refuses is now an ordinary failed result instead of a panic that lost the sequence number and stalled every ordered callback behind it forever.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 As natives de submissão passaram a dizer a verdade</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span>, <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>, <span style="font-weight: bold;" class="mycode_b">mysql_stmt_execute</span>, <span style="font-weight: bold;" class="mycode_b">mysql_stmt_pexecute</span>, <span style="font-weight: bold;" class="mycode_b">mysql_transaction_execute</span> e as natives CRUD do ORM devolviam <span style="font-weight: bold;" class="mycode_b">true</span> sempre que o id da conexão era válido. Agora devolvem o que a submissão realmente fez.<br />
🇬🇧 They returned <span style="font-weight: bold;" class="mycode_b">true</span> whenever the connection id was valid; they now return what the submission actually did.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Pedir TLS em host local rodava em texto claro</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL = 1</span> contra <span style="font-weight: bold;" class="mycode_b">127.0.0.1</span> reportava sucesso enquanto o cipher voltava vazio. O driver tem <span style="font-weight: bold;" class="mycode_b">prefer_socket</span> ligado por padrão e, depois do handshake, move uma conexão de loopback para o socket unix do servidor — onde ela não protege nada. As duas condições se encontravam na configuração local mais comum que existe. Pedir TLS agora desliga essa otimização; sem TLS ela continua, que é onde ajuda. Verificado contra MariaDB 11.8: <span style="font-weight: bold;" class="mycode_b">TLS_AES_256_GCM_SHA384</span> onde antes havia string vazia.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL = 1</span> against <span style="font-weight: bold;" class="mycode_b">127.0.0.1</span> reported success with an empty cipher: the driver moves a loopback connection onto the unix socket after the handshake, where it secures nothing. Asking for TLS now switches that off.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ainda / 🇬🇧 And also</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Erro do driver chegava ao Pawn com entranha de Rust</span> — tabela faltando vinha como <span style="font-weight: bold;" class="mycode_b">MySqlError { ERROR 1146 (42S02): Table 'db.t' doesn't exist }</span>. Agora passa a mensagem do servidor, sozinha / 🇬🇧 Rust internals no longer wrap the server's own message<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query_file falhava sem avisar</span> — arquivo ilegível ou sem statements era só uma linha de log, que a forma sem callback (a recomendada para schema) nunca vê. Agora dispara <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, com o caminho no lugar da query / 🇬🇧 now fires <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, naming the file<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">rustls 0.23.45 (RUSTSEC-2026-0285)</span> — mensagens de handshake TLS 1.3 eram aceitas atravessando limites de nível de criptografia. O transcript segue autenticado, então ninguém na rede altera ou completa um handshake; o efeito prático é um par conseguir mandar em texto claro o que deveria ir cifrado / 🇬🇧 TLS 1.3 handshake messages accepted across encryption level boundaries<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🆕 MYSQL_SYNC: bloquear quando a chamada pede</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">New: MYSQL_SYNC</span></span><br />
<br />
🇧🇷 Até a 1.3.0 não havia caminho bloqueante na API, de propósito. O problema é que existe trabalho que não é sobre desempenho: criar tabela no <span style="font-weight: bold;" class="mycode_b">OnGameModeInit</span>, ler uma configuração que a linha seguinte precisa, migrar schema. Exigir callback para isso é cerimônia, e a resposta para "como eu leio isto agora?" não podia ser "não leia".<br />
<br />
🇬🇧 Up to 1.3.0 there was no blocking path in the API, by design. But some work isn't about performance: creating tables in <span style="font-weight: bold;" class="mycode_b">OnGameModeInit</span>, reading a setting the next line needs, migrating schema.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT COUNT(*) AS n FROM contas", MYSQL_SYNC);<br />
new total = cache_get_value_name_int(0, "n");  // o resultado já está aqui</code></div></div><br />
🇧🇷 Funciona em <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> e <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> — onde vive o trabalho de schema na subida do servidor.<br />
🇬🇧 Supported on <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> and <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Por que é um valor e não uma native nova</span><br />
🇧🇷 Porque uma native chamada <span style="font-weight: bold;" class="mycode_b">mysql_query_sync</span> acaba dentro do <span style="font-weight: bold;" class="mycode_b">OnPlayerUpdate</span>. Um valor escrito no lugar do callback põe a intenção onde o custo é pago: quem lê a linha vê que ela espera. E não podia ser um argumento novo — o Pawn não aceita parâmetro depois de <span style="font-weight: bold;" class="mycode_b">{Float,_}:...</span>, e colocá-lo antes do callback quebraria toda chamada existente.<br />
🇬🇧 A native called <span style="font-weight: bold;" class="mycode_b">mysql_query_sync</span> ends up inside <span style="font-weight: bold;" class="mycode_b">OnPlayerUpdate</span>. A value at the call site states the intent where the cost is paid. It couldn't be a new argument either: Pawn refuses any parameter after <span style="font-weight: bold;" class="mycode_b">{Float,_}:...</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">As três regras que mantêm isso honesto</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Recusado dentro de callback</span> — lá "o cache atual" já significa o do próprio callback; um resultado bloqueante ou o encobriria ou ficaria ilegível. A recusa vai para o log, não passa calada / 🇬🇧 refused inside a callback, out loud<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Liberado no tick seguinte</span> — a mesma garantia que o caminho assíncrono ganha do callback retornar. Nada para liberar à mão / 🇬🇧 freed at the next server tick, nothing to free by hand<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Recusado nas natives que não podem honrá-lo</span> — prepared statements, transações, ORM e hash de senha. Aceito em silêncio, o valor passaria por nome de callback que nunca dispara, e a chamada viraria fire-and-forget sem você saber / 🇬🇧 refused on every native that cannot honour it — otherwise the call would silently become fire-and-forget<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚖️ Como usar assíncrono e síncrono</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Using async and sync</span></span><br />
<br />
🇧🇷 A regra em uma frase: <span style="font-weight: bold;" class="mycode_b">se um jogador está esperando, é assíncrono; se o servidor ainda está subindo, pode ser síncrono.</span><br />
🇬🇧 One sentence: if a player is waiting, async. If the server is still starting up, sync is fine.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Assíncrono com callback — o caso normal</span><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT id, nome FROM contas WHERE nome = 'x'", "OnContaCarregada", "d", playerid);<br />
<br />
forward OnContaCarregada(playerid);<br />
public OnContaCarregada(playerid)<br />
{<br />
    if (!cache_get_row_count()) return 0;  // conta não existe<br />
<br />
    new nome[MAX_PLAYER_NAME];<br />
    cache_get_value_name(0, "nome", nome);<br />
    printf("conta de %s carregada", nome);<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">2. Assíncrono sem callback — quando o resultado não interessa</span><br />
🇧🇷 O callback sempre foi opcional, mas isso estava num parágrafo no pé de uma página enquanto todos os exemplos sugeriam o contrário — daí a reclamação de que o plugin "obriga a escrever callback para tudo". Um <span style="font-weight: bold;" class="mycode_b">UPDATE</span> cujo resultado você não vai ler é uma linha:<br />
🇬🇧 The callback was always optional; the docs buried it. An <span style="font-weight: bold;" class="mycode_b">UPDATE</span> whose result you won't read is one line:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "UPDATE contas SET online = 0 WHERE id = 7");</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">3. Síncrono — trabalho de subida</span><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>public OnGameModeInit()<br />
{<br />
    g_mysql = mysql_connect_file();                      // credenciais fora do código<br />
<br />
    mysql_query_file(g_mysql, "schema.sql", MYSQL_SYNC); // schema pronto ANTES de seguir<br />
<br />
    mysql_query(g_mysql, "SELECT valor FROM config WHERE chave = 'preco'", MYSQL_SYNC);<br />
    g_preco = cache_get_value_name_int(0, "valor");      // já disponível aqui<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">4. FIFO ordena callbacks, não execução — a armadilha</span><br />
🇧🇷 Isto merece destaque porque a documentação antiga recomendava errado. O <span style="font-weight: bold;" class="mycode_b">mysql_query</span> entrega os callbacks na ordem em que você submeteu, mas cada statement roda na sua própria conexão, ao mesmo tempo. Um <span style="font-weight: bold;" class="mycode_b">CREATE TABLE</span> seguido de um <span style="font-weight: bold;" class="mycode_b">INSERT</span> corre junto, e o insert falha. Quem serializa de verdade é o <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> (arquivo inteiro, em ordem) e a transação (lote atômico) — ou o <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>, que simplesmente espera.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">mysql_query</span> dispatches callbacks in submission order, but each statement runs on its own connection, concurrently: a <span style="font-weight: bold;" class="mycode_b">CREATE TABLE</span> followed by an <span style="font-weight: bold;" class="mycode_b">INSERT</span> races. What actually serialises is <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>, a transaction, or <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. E o mysql_pquery?</span><br />
🇧🇷 Entrega assim que cada uma termina, sem ordem garantida — bom para muitas queries independentes. Com <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> ele é igual ao <span style="font-weight: bold;" class="mycode_b">mysql_query</span>: uma chamada que espera não tem com o que rodar em paralelo.<br />
🇬🇧 Delivers as each finishes, no ordering. With <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> it's identical to the FIFO native.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔐 Outras novidades</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Also new in 1.4.0</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Diagnóstico de TLS</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_tls_active(connId)</span> e <span style="font-weight: bold;" class="mycode_b">mysql_tls_cipher(connId, dest[], max_len)</span> dizem no que o handshake realmente terminou, não o que o <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL</span> pediu. Os dois leem um valor capturado quando a conexão abriu, então nenhum custa uma query. Sessão sem criptografia devolve <span style="font-weight: bold;" class="mycode_b">false</span> e cipher vazio — essa é a resposta, e o retorno separa isso de uma conexão que não existe.<br />
🇬🇧 What the handshake actually settled on, rather than what <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL</span> asked for. Both read a value captured when the connection opened, so neither costs a query.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>if (!mysql_tls_active(g_mysql)) print("[MySQL] atenção: conexão em texto claro");<br />
else<br />
{<br />
    new cipher[64];<br />
    mysql_tls_cipher(g_mysql, cipher, sizeof(cipher));<br />
    printf("[MySQL] TLS: %s", cipher);          // ex.: TLS_AES_256_GCM_SHA384<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Os tetos de memória deixaram de ser fixos</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_limit_set</span> e <span style="font-weight: bold;" class="mycode_b">mysql_limit_get</span> mexem em três limites antes cravados no código: <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_SAVED_CACHES</span> (1024), <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_RESULT_ROWS</span> (100000) e <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_ORM_STRING_LEN</span> (4096). Quem precisa de mais, aumenta; máquina apertada, diminui. São globais e não por conexão, porque a memória protegida é a do servidor. O valor <span style="font-weight: bold;" class="mycode_b">0</span> é recusado em vez de ser lido como "ilimitado" — ilimitado é justamente o estado que esses limites existem para evitar — e abaixar um teto não joga fora o que já está guardado.<br />
🇬🇧 Three caps that used to be hardcoded. Global, not per connection. <span style="font-weight: bold;" class="mycode_b">0</span> is refused rather than read as "unlimited", and lowering a cap keeps what is already held.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_limit_set(MYSQL_LIMIT_RESULT_ROWS, 250000);  // relatório grande<br />
printf("caches guardados: %d", mysql_limit_get(MYSQL_LIMIT_SAVED_CACHES));</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">&#36;{VARIÁVEL} no arquivo de conexão</span><br />
🇧🇷 O <span style="font-weight: bold;" class="mycode_b">mysql_connect_file</span> já tirava a senha do gamemode; agora o arquivo pode citar o segredo em vez de guardá-lo, e uma cópia roubada não vale nada por si:<br />
🇬🇧 The file can now name the secret instead of holding it:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>host = 127.0.0.1<br />
user = samp<br />
password = &#36;{MYSQL_PASSWORD}<br />
database = meu_banco</code></div></div><br />
🇧🇷 Nome não definido expande para vazio e diz no log qual faltou — mandar o literal <span style="font-weight: bold;" class="mycode_b">&#36;{MYSQL_PASSWORD}</span> para o servidor falharia como "access denied" e mandaria você procurar no lugar errado. Só <span style="font-weight: bold;" class="mycode_b">&#36;{...}</span> é referência, então senha com sinal de dólar continua funcionando.<br />
🇬🇧 An unset name expands to nothing and logs which one was missing. Only <span style="font-weight: bold;" class="mycode_b">&#36;{...}</span> is a reference, so a password with a dollar sign still works.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">IPv6</span><br />
🇧🇷 É suportado, entre colchetes: <span style="font-weight: bold;" class="mycode_b">[::1]</span> é interpretado e validado como endereço. Sem colchetes vira nome de host — normalmente ainda conecta, mas não valida nada.<br />
🇬🇧 Supported, in brackets: <span style="font-weight: bold;" class="mycode_b">[::1]</span> is parsed and validated as an address; without brackets it's taken as a host name.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚙️ Por dentro: o SDK e o que ele destravou</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Under the hood</span></span><br />
<br />
🇧🇷 O plugin roda sobre o <a href="https://github.com/NullSablex/rust-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">rust-samp</span></a>, o SDK em Rust para SA-MP e open.mp, que aqui vai para a <span style="font-weight: bold;" class="mycode_b">3.6.0</span> e sai de uma tag do git para o crates.io. A mudança já conserta uma esquisitice: a tag <span style="font-weight: bold;" class="mycode_b">v3.5.0</span> declarava <span style="font-weight: bold;" class="mycode_b">version = "3.4.0"</span> no próprio manifesto, então o lockfile registrava 3.4.0 apontando para a tag v3.5.0.<br />
<br />
🇧🇷 O que a 3.6.0 trouxe de útil aqui foi uma rota que faltava. Quando uma query termina numa worker thread, o resultado precisa aterrissar no estado do plugin — e até a 3.5.0 não havia caminho do SDK para isso, limitação que o plugin contornava com canal próprio. O <span style="font-weight: bold;" class="mycode_b">post_with</span> entrega o plugin ao job, e com ele os canais de resultado do plugin deixaram de existir, tanto na query quanto no hash de senha.<br />
<br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">Não é ganho de desempenho, e eu não vou vender como se fosse.</span> O SDK drena essa fila imediatamente antes do <span style="font-weight: bold;" class="mycode_b">on_tick</span>, então o despacho continua acontecendo no <span style="font-weight: bold;" class="mycode_b">on_tick</span> e nada chega um tick depois do que chegava. O ganho é de arquitetura: menos código próprio fazendo o que o SDK passou a fazer, num ponto onde errar custa caro. Os ganhos de robustez que você sente nesta versão — a rajada de 9000 queries — são do plugin, não do SDK.<br />
🇬🇧 It is not a performance win and I won't sell it as one. The SDK drains that queue immediately before <span style="font-weight: bold;" class="mycode_b">on_tick</span>, so dispatch still happens there and nothing arrives a tick later than it used to. The gain is architectural. The robustness wins you feel in this release are the plugin's, not the SDK's.<br />
<br />
🇧🇷 Um detalhe de projeto que não é questão de gosto: entregar o resultado e despachar o callback tiveram de ficar em dois passos. Chamar o Pawn de dentro do job deixaria um <span style="font-weight: bold;" class="mycode_b">public</span> reentrar numa native e pegar um segundo empréstimo mutável do mesmo plugin, que é instantaneamente um bug de memória. Então o job só bufferiza, e quem invoca callback é o <span style="font-weight: bold;" class="mycode_b">on_tick</span>, que detém esse empréstimo legitimamente. A ordem FIFO continua sendo nossa, no buffer de sequência, porque os jobs terminam na ordem em que o banco responde, não na que você pediu.<br />
🇬🇧 Handing the result over and dispatching the callback had to be two steps: calling Pawn from inside the job would let a <span style="font-weight: bold;" class="mycode_b">public</span> re-enter a native and take a second mutable borrow of the plugin. The job only buffers; <span style="font-weight: bold;" class="mycode_b">on_tick</span> dispatches.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Gargalo antigo que já não é seu problema</span><br />
🇧🇷 Vindo de outro plugin MySQL você provavelmente aprendeu a chamar <span style="font-weight: bold;" class="mycode_b">mysql_tick()</span> num timer. Aqui não é necessário desde a 3.0.0 do SDK: a fila é bombeada a cada tick, sozinha. A native continua existindo só por compatibilidade — e ela não libera o resultado do <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>, que sai no tick seguinte de todo jeito.<br />
🇬🇧 Coming from another MySQL plugin you probably learned to call <span style="font-weight: bold;" class="mycode_b">mysql_tick()</span> on a timer. Not needed here since SDK 3.0.0: the queue is pumped every tick automatically.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📖 A referência da API agora se escreve sozinha</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">The API reference generates itself</span></span><br />
<br />
🇧🇷 A <a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">referência da API</span></a> era uma tabela escrita à mão com cada native, seu tipo e uma descrição — exatamente a informação que o include já carrega ao lado de cada declaração, mantida em sincronia a dedo. Agora ela é gerada do include a cada build: natives, o forward, as constantes documentadas e os enums com seus valores, cada um com assinatura, apelido open.mp, parâmetros e retorno. A cópia manual foi apagada, e a URL é a mesma.<br />
<br />
🇧🇷 O gerador lê dois formatos de comentário: o <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> (<span style="font-weight: bold;" class="mycode_b">@param</span>, <span style="font-weight: bold;" class="mycode_b">@return</span>), que é o que estes includes usam, e o <span style="font-weight: bold;" class="mycode_b">pawndoc</span> (<span style="font-weight: bold;" class="mycode_b">&lt;summary&gt;</span>, <span style="font-weight: bold;" class="mycode_b">&lt;param name=""&gt;</span>, <span style="font-weight: bold;" class="mycode_b">&lt;returns&gt;</span>), que é o que você encontra em includes de terceiros. E tem autoteste: se uma native entrar sem bloco de documentação, o build da doc falha em vez de publicar uma linha vazia.<br />
<br />
🇧🇷 Esses mesmos comentários chegam ao hover do seu editor — na extensão <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> para VS Code, por exemplo, você passa o mouse na native e lê o que cada parâmetro espera sem abrir o <span style="font-weight: bold;" class="mycode_b">.inc</span>. Nesta versão as três natives que aceitam <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> carregam o exemplo bloqueante no próprio comentário, então o jeito de fazer uma chamada esperar aparece onde a native é documentada, não só na página de queries.<br />
<br />
🇬🇧 The API reference is now generated from the includes on every docs build — natives, forward, documented constants and enums with their values. Two comment formats are read: <span style="font-weight: bold;" class="mycode_b">JavaDoc</span>, used here, and <span style="font-weight: bold;" class="mycode_b">pawndoc</span>. A native without a doc block fails the docs build. The same comments reach your editor's hover — e.g. the <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> VS Code extension.<br />
<br />
<hr class="mycode_hr" />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Prepared statement continua sendo a via segura. O <span style="font-weight: bold;" class="mycode_b">mysql_format</span> e o ORM interpolam texto e dependem do escaping; o <span style="font-weight: bold;" class="mycode_b">mysql_stmt_*</span> manda os valores pelo protocolo binário e não tem esse risco. Para qualquer coisa que carregue o que o jogador digitou, prefira statement — isso não mudou nesta versão e não vai mudar.<br />
🇬🇧 Prepared statements are still the safe path for anything carrying player input.</blockquote>
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download da 1.4.0</span></a> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">.so</span>, <span style="font-weight: bold;" class="mycode_b">.dll</span> e os dois includes / 🇬🇧 binaries and both includes<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 conexão, queries, cache, ORM, opções e segurança / 🇬🇧 connection, queries, cache, ORM, options, security<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 79 natives, geradas do include / 🇬🇧 all 79 natives, generated from the include<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/migration/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Guia de migração</span></a> — 🇧🇷 para quem vem de outro plugin MySQL / 🇬🇧 coming from another MySQL plugin<br />
</li>
<li><a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG completo</span></a> — 🇧🇷 com o que não caberia aqui / 🇬🇧 the full list<br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Projeto independente, mantido por uma pessoa. Não é afiliado, endossado nem patrocinado pelo SA-MP ou pelo open.mp. "SA-MP", "open.mp" e "MySQL" pertencem a seus respectivos donos e são citados apenas para descrever compatibilidade.<br />
🇬🇧 Independent project, maintained by one person. Not affiliated with or endorsed by SA-MP or open.mp.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/mysql_samp</span></a></div>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">🦀 mysql_samp 1.4.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">MySQL para SA-MP e open.mp — o mesmo binário nos dois</span><br />
<span style="font-style: italic;" class="mycode_i">MySQL for SA-MP and open.mp — the same binary on both</span></span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> · <a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Plugin MySQL em Rust, <span style="font-weight: bold;" class="mycode_b">79 natives</span>, sem dependência para instalar. Esta versão, em três linhas:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Uma mudança de comportamento:</span> o <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> entregava seus cinco argumentos na ordem invertida, desde a 1.0.0. Se você trata esse forward, confira o handler.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Uma correção de estabilidade:</span> uma rajada de queries num único tick podia abortar o servidor e travar a fila de callbacks permanentemente.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Uma novidade:</span> <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> faz uma chamada específica esperar o banco, sem callback.<br />
</li>
</ul>
<br />
🇧🇷 Nenhuma native foi removida ou renomeada e nenhuma assinatura mudou: gamemode de 1.3.0 compila como está.<br />
<br />
🇬🇧 MySQL plugin in Rust, <span style="font-weight: bold;" class="mycode_b">79 natives</span>, nothing to install. One behaviour change (<span style="font-weight: bold;" class="mycode_b">OnQueryError</span> delivered its five arguments reversed since 1.0.0 — check your handler), one stability fix (a burst of queries in one tick could abort the server and stall the callback queue for good), one new feature (<span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> makes a single call wait on the database, with no callback). Nothing removed, renamed or re-signed: 1.3.0 gamemodes compile unchanged.</blockquote>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚠️ Mudança de comportamento: OnQueryError</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Behaviour change: OnQueryError</span></span><br />
<br />
🇧🇷 O forward é declarado assim:<br />
🇬🇧 The forward is declared as:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>forward OnQueryError(errorid, error[], callback[], query[], connId);</code></div></div><br />
🇧🇷 Até a 1.3.0 o gamemode recebia os campos ao contrário: o id da conexão chegava como <span style="font-weight: bold;" class="mycode_b">errorid</span>, o texto da query como <span style="font-weight: bold;" class="mycode_b">error</span>, a mensagem de erro como <span style="font-weight: bold;" class="mycode_b">query</span> e o código do MySQL como <span style="font-weight: bold;" class="mycode_b">connId</span>.<br />
<br />
🇧🇷 A causa estava no despacho. O <span style="font-weight: bold;" class="mycode_b">exec_public!</span> empilha os argumentos ao contrário — é a convenção da pilha do AMX — logo a ordem escrita na chamada já é a ordem que o <span style="font-weight: bold;" class="mycode_b">public</span> recebe. O código os listava de trás para frente, como se tivesse de inverter à mão.<br />
<br />
🇧🇷 A inversão era consistente, então um handler podia parecer funcionar há bastante tempo lendo o id da conexão como código de erro. <span style="font-weight: bold;" class="mycode_b">Presente desde a 1.0.0</span> — se você trata esse forward, é o primeiro lugar a olhar depois de atualizar.<br />
<br />
🇬🇧 Up to 1.3.0 the fields arrived reversed: connection id as <span style="font-weight: bold;" class="mycode_b">errorid</span>, query text as <span style="font-weight: bold;" class="mycode_b">error</span>, error message as <span style="font-weight: bold;" class="mycode_b">query</span>, MySQL code as <span style="font-weight: bold;" class="mycode_b">connId</span>. <span style="font-weight: bold;" class="mycode_b">exec_public!</span> pushes arguments in reverse (the AMX stack convention), so the order written at the call site is already the order the <span style="font-weight: bold;" class="mycode_b">public</span> receives; the code listed them backwards as if it had to invert them by hand. Consistent enough that a handler could appear to work for a long time. Present since 1.0.0.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ao atualizar / 🇬🇧 And when updating</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Troque o binário e os includes.</span> O release traz <span style="font-weight: bold;" class="mycode_b">.so</span>, <span style="font-weight: bold;" class="mycode_b">.dll</span>, <span style="font-weight: bold;" class="mycode_b">mysql_samp.inc</span> e <span style="font-weight: bold;" class="mycode_b">mysql_samp_omp.inc</span>. Sem o include novo não existem <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> nem as natives novas — e include velho com binário novo é o erro mais comum numa atualização. O <span style="font-weight: bold;" class="mycode_b">MYSQL_SAMP_VERSION</span> do include serve justamente para comparar na subida / 🇬🇧 replace the binary and the includes; <span style="font-weight: bold;" class="mycode_b">MYSQL_SAMP_VERSION</span> exists to catch a stale one<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Binário de 32 bits</span>, como o servidor. SA-MP em <span style="font-weight: bold;" class="mycode_b">plugins/</span>; open.mp nativo em <span style="font-weight: bold;" class="mycode_b">components/</span>, sem mexer no <span style="font-weight: bold;" class="mycode_b">config.json</span> / 🇬🇧 <span style="font-weight: bold;" class="mycode_b">32-bit</span>, like the server<br />
</li>
<li>🇧🇷 Fora o handler do <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, nada mais a fazer / 🇬🇧 apart from the <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> handler, nothing else to do<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🐛 Correções</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Fixed</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Uma rajada de queries matava o servidor — e podia travar a fila para sempre</span><br />
🇧🇷 Submeter 2000 queries num tick abortava o processo com <span style="font-weight: bold;" class="mycode_b">memory allocation of 4194304 bytes failed</span>. A causa é específica desta arquitetura: uma thread por query, com a pilha padrão de 8 MiB, dentro de um servidor <span style="font-weight: bold;" class="mycode_b">32 bits</span>. Algumas centenas de workers vivos esgotam o espaço de endereçamento, e a próxima alocação que falha leva o processo junto. Foi achado por teste de força bruta, não por relato.<br />
<br />
🇧🇷 Eram dois problemas, os dois corrigidos:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Pilha de 256 KiB por worker</span> — um worker abre conexão, roda um statement e monta um cache; não recursa. O teto medido saiu de cerca de 1700 para mais de 9000 workers<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Thread recusada pelo sistema não é mais pânico</span> — e esta parte era pior que o crash: a submissão se perdia junto com o número de sequência, de modo que toda callback ordenada atrás dela esperaria para sempre. Agora é um resultado com erro comum: a sequência é contabilizada, o <span style="font-weight: bold;" class="mycode_b">OnQueryError</span> dispara e a native devolve <span style="font-weight: bold;" class="mycode_b">false</span><br />
</li>
</ul>
🇧🇷 Medido depois da correção: 9000 queries num tick, 9000 callbacks entregues, ordem intacta, nada preso no caminho.<br />
🇬🇧 2000 queries in one tick aborted the process — one thread per query, 8 MiB default stack, <span style="font-weight: bold;" class="mycode_b">32-bit</span> server. Workers now get a 256 KiB stack (ceiling moved from ~1700 to past 9000), and a thread the OS refuses is now an ordinary failed result instead of a panic that lost the sequence number and stalled every ordered callback behind it forever.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 As natives de submissão passaram a dizer a verdade</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span>, <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>, <span style="font-weight: bold;" class="mycode_b">mysql_stmt_execute</span>, <span style="font-weight: bold;" class="mycode_b">mysql_stmt_pexecute</span>, <span style="font-weight: bold;" class="mycode_b">mysql_transaction_execute</span> e as natives CRUD do ORM devolviam <span style="font-weight: bold;" class="mycode_b">true</span> sempre que o id da conexão era válido. Agora devolvem o que a submissão realmente fez.<br />
🇬🇧 They returned <span style="font-weight: bold;" class="mycode_b">true</span> whenever the connection id was valid; they now return what the submission actually did.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Pedir TLS em host local rodava em texto claro</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL = 1</span> contra <span style="font-weight: bold;" class="mycode_b">127.0.0.1</span> reportava sucesso enquanto o cipher voltava vazio. O driver tem <span style="font-weight: bold;" class="mycode_b">prefer_socket</span> ligado por padrão e, depois do handshake, move uma conexão de loopback para o socket unix do servidor — onde ela não protege nada. As duas condições se encontravam na configuração local mais comum que existe. Pedir TLS agora desliga essa otimização; sem TLS ela continua, que é onde ajuda. Verificado contra MariaDB 11.8: <span style="font-weight: bold;" class="mycode_b">TLS_AES_256_GCM_SHA384</span> onde antes havia string vazia.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL = 1</span> against <span style="font-weight: bold;" class="mycode_b">127.0.0.1</span> reported success with an empty cipher: the driver moves a loopback connection onto the unix socket after the handshake, where it secures nothing. Asking for TLS now switches that off.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ainda / 🇬🇧 And also</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Erro do driver chegava ao Pawn com entranha de Rust</span> — tabela faltando vinha como <span style="font-weight: bold;" class="mycode_b">MySqlError { ERROR 1146 (42S02): Table 'db.t' doesn't exist }</span>. Agora passa a mensagem do servidor, sozinha / 🇬🇧 Rust internals no longer wrap the server's own message<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query_file falhava sem avisar</span> — arquivo ilegível ou sem statements era só uma linha de log, que a forma sem callback (a recomendada para schema) nunca vê. Agora dispara <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, com o caminho no lugar da query / 🇬🇧 now fires <span style="font-weight: bold;" class="mycode_b">OnQueryError</span>, naming the file<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">rustls 0.23.45 (RUSTSEC-2026-0285)</span> — mensagens de handshake TLS 1.3 eram aceitas atravessando limites de nível de criptografia. O transcript segue autenticado, então ninguém na rede altera ou completa um handshake; o efeito prático é um par conseguir mandar em texto claro o que deveria ir cifrado / 🇬🇧 TLS 1.3 handshake messages accepted across encryption level boundaries<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🆕 MYSQL_SYNC: bloquear quando a chamada pede</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">New: MYSQL_SYNC</span></span><br />
<br />
🇧🇷 Até a 1.3.0 não havia caminho bloqueante na API, de propósito. O problema é que existe trabalho que não é sobre desempenho: criar tabela no <span style="font-weight: bold;" class="mycode_b">OnGameModeInit</span>, ler uma configuração que a linha seguinte precisa, migrar schema. Exigir callback para isso é cerimônia, e a resposta para "como eu leio isto agora?" não podia ser "não leia".<br />
<br />
🇬🇧 Up to 1.3.0 there was no blocking path in the API, by design. But some work isn't about performance: creating tables in <span style="font-weight: bold;" class="mycode_b">OnGameModeInit</span>, reading a setting the next line needs, migrating schema.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT COUNT(*) AS n FROM contas", MYSQL_SYNC);<br />
new total = cache_get_value_name_int(0, "n");  // o resultado já está aqui</code></div></div><br />
🇧🇷 Funciona em <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> e <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> — onde vive o trabalho de schema na subida do servidor.<br />
🇬🇧 Supported on <span style="font-weight: bold;" class="mycode_b">mysql_query</span>, <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> and <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Por que é um valor e não uma native nova</span><br />
🇧🇷 Porque uma native chamada <span style="font-weight: bold;" class="mycode_b">mysql_query_sync</span> acaba dentro do <span style="font-weight: bold;" class="mycode_b">OnPlayerUpdate</span>. Um valor escrito no lugar do callback põe a intenção onde o custo é pago: quem lê a linha vê que ela espera. E não podia ser um argumento novo — o Pawn não aceita parâmetro depois de <span style="font-weight: bold;" class="mycode_b">{Float,_}:...</span>, e colocá-lo antes do callback quebraria toda chamada existente.<br />
🇬🇧 A native called <span style="font-weight: bold;" class="mycode_b">mysql_query_sync</span> ends up inside <span style="font-weight: bold;" class="mycode_b">OnPlayerUpdate</span>. A value at the call site states the intent where the cost is paid. It couldn't be a new argument either: Pawn refuses any parameter after <span style="font-weight: bold;" class="mycode_b">{Float,_}:...</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">As três regras que mantêm isso honesto</span><ul class="mycode_list"><li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Recusado dentro de callback</span> — lá "o cache atual" já significa o do próprio callback; um resultado bloqueante ou o encobriria ou ficaria ilegível. A recusa vai para o log, não passa calada / 🇬🇧 refused inside a callback, out loud<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Liberado no tick seguinte</span> — a mesma garantia que o caminho assíncrono ganha do callback retornar. Nada para liberar à mão / 🇬🇧 freed at the next server tick, nothing to free by hand<br />
</li>
<li>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Recusado nas natives que não podem honrá-lo</span> — prepared statements, transações, ORM e hash de senha. Aceito em silêncio, o valor passaria por nome de callback que nunca dispara, e a chamada viraria fire-and-forget sem você saber / 🇬🇧 refused on every native that cannot honour it — otherwise the call would silently become fire-and-forget<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚖️ Como usar assíncrono e síncrono</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Using async and sync</span></span><br />
<br />
🇧🇷 A regra em uma frase: <span style="font-weight: bold;" class="mycode_b">se um jogador está esperando, é assíncrono; se o servidor ainda está subindo, pode ser síncrono.</span><br />
🇬🇧 One sentence: if a player is waiting, async. If the server is still starting up, sync is fine.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">1. Assíncrono com callback — o caso normal</span><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT id, nome FROM contas WHERE nome = 'x'", "OnContaCarregada", "d", playerid);<br />
<br />
forward OnContaCarregada(playerid);<br />
public OnContaCarregada(playerid)<br />
{<br />
    if (!cache_get_row_count()) return 0;  // conta não existe<br />
<br />
    new nome[MAX_PLAYER_NAME];<br />
    cache_get_value_name(0, "nome", nome);<br />
    printf("conta de %s carregada", nome);<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">2. Assíncrono sem callback — quando o resultado não interessa</span><br />
🇧🇷 O callback sempre foi opcional, mas isso estava num parágrafo no pé de uma página enquanto todos os exemplos sugeriam o contrário — daí a reclamação de que o plugin "obriga a escrever callback para tudo". Um <span style="font-weight: bold;" class="mycode_b">UPDATE</span> cujo resultado você não vai ler é uma linha:<br />
🇬🇧 The callback was always optional; the docs buried it. An <span style="font-weight: bold;" class="mycode_b">UPDATE</span> whose result you won't read is one line:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "UPDATE contas SET online = 0 WHERE id = 7");</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">3. Síncrono — trabalho de subida</span><br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>public OnGameModeInit()<br />
{<br />
    g_mysql = mysql_connect_file();                      // credenciais fora do código<br />
<br />
    mysql_query_file(g_mysql, "schema.sql", MYSQL_SYNC); // schema pronto ANTES de seguir<br />
<br />
    mysql_query(g_mysql, "SELECT valor FROM config WHERE chave = 'preco'", MYSQL_SYNC);<br />
    g_preco = cache_get_value_name_int(0, "valor");      // já disponível aqui<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">4. FIFO ordena callbacks, não execução — a armadilha</span><br />
🇧🇷 Isto merece destaque porque a documentação antiga recomendava errado. O <span style="font-weight: bold;" class="mycode_b">mysql_query</span> entrega os callbacks na ordem em que você submeteu, mas cada statement roda na sua própria conexão, ao mesmo tempo. Um <span style="font-weight: bold;" class="mycode_b">CREATE TABLE</span> seguido de um <span style="font-weight: bold;" class="mycode_b">INSERT</span> corre junto, e o insert falha. Quem serializa de verdade é o <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> (arquivo inteiro, em ordem) e a transação (lote atômico) — ou o <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>, que simplesmente espera.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">mysql_query</span> dispatches callbacks in submission order, but each statement runs on its own connection, concurrently: a <span style="font-weight: bold;" class="mycode_b">CREATE TABLE</span> followed by an <span style="font-weight: bold;" class="mycode_b">INSERT</span> races. What actually serialises is <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span>, a transaction, or <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">5. E o mysql_pquery?</span><br />
🇧🇷 Entrega assim que cada uma termina, sem ordem garantida — bom para muitas queries independentes. Com <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> ele é igual ao <span style="font-weight: bold;" class="mycode_b">mysql_query</span>: uma chamada que espera não tem com o que rodar em paralelo.<br />
🇬🇧 Delivers as each finishes, no ordering. With <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> it's identical to the FIFO native.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔐 Outras novidades</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Also new in 1.4.0</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Diagnóstico de TLS</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_tls_active(connId)</span> e <span style="font-weight: bold;" class="mycode_b">mysql_tls_cipher(connId, dest[], max_len)</span> dizem no que o handshake realmente terminou, não o que o <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL</span> pediu. Os dois leem um valor capturado quando a conexão abriu, então nenhum custa uma query. Sessão sem criptografia devolve <span style="font-weight: bold;" class="mycode_b">false</span> e cipher vazio — essa é a resposta, e o retorno separa isso de uma conexão que não existe.<br />
🇬🇧 What the handshake actually settled on, rather than what <span style="font-weight: bold;" class="mycode_b">MYSQL_OPT_SSL</span> asked for. Both read a value captured when the connection opened, so neither costs a query.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>if (!mysql_tls_active(g_mysql)) print("[MySQL] atenção: conexão em texto claro");<br />
else<br />
{<br />
    new cipher[64];<br />
    mysql_tls_cipher(g_mysql, cipher, sizeof(cipher));<br />
    printf("[MySQL] TLS: %s", cipher);          // ex.: TLS_AES_256_GCM_SHA384<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Os tetos de memória deixaram de ser fixos</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_limit_set</span> e <span style="font-weight: bold;" class="mycode_b">mysql_limit_get</span> mexem em três limites antes cravados no código: <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_SAVED_CACHES</span> (1024), <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_RESULT_ROWS</span> (100000) e <span style="font-weight: bold;" class="mycode_b">MYSQL_LIMIT_ORM_STRING_LEN</span> (4096). Quem precisa de mais, aumenta; máquina apertada, diminui. São globais e não por conexão, porque a memória protegida é a do servidor. O valor <span style="font-weight: bold;" class="mycode_b">0</span> é recusado em vez de ser lido como "ilimitado" — ilimitado é justamente o estado que esses limites existem para evitar — e abaixar um teto não joga fora o que já está guardado.<br />
🇬🇧 Three caps that used to be hardcoded. Global, not per connection. <span style="font-weight: bold;" class="mycode_b">0</span> is refused rather than read as "unlimited", and lowering a cap keeps what is already held.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_limit_set(MYSQL_LIMIT_RESULT_ROWS, 250000);  // relatório grande<br />
printf("caches guardados: %d", mysql_limit_get(MYSQL_LIMIT_SAVED_CACHES));</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">&#36;{VARIÁVEL} no arquivo de conexão</span><br />
🇧🇷 O <span style="font-weight: bold;" class="mycode_b">mysql_connect_file</span> já tirava a senha do gamemode; agora o arquivo pode citar o segredo em vez de guardá-lo, e uma cópia roubada não vale nada por si:<br />
🇬🇧 The file can now name the secret instead of holding it:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>host = 127.0.0.1<br />
user = samp<br />
password = &#36;{MYSQL_PASSWORD}<br />
database = meu_banco</code></div></div><br />
🇧🇷 Nome não definido expande para vazio e diz no log qual faltou — mandar o literal <span style="font-weight: bold;" class="mycode_b">&#36;{MYSQL_PASSWORD}</span> para o servidor falharia como "access denied" e mandaria você procurar no lugar errado. Só <span style="font-weight: bold;" class="mycode_b">&#36;{...}</span> é referência, então senha com sinal de dólar continua funcionando.<br />
🇬🇧 An unset name expands to nothing and logs which one was missing. Only <span style="font-weight: bold;" class="mycode_b">&#36;{...}</span> is a reference, so a password with a dollar sign still works.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">IPv6</span><br />
🇧🇷 É suportado, entre colchetes: <span style="font-weight: bold;" class="mycode_b">[::1]</span> é interpretado e validado como endereço. Sem colchetes vira nome de host — normalmente ainda conecta, mas não valida nada.<br />
🇬🇧 Supported, in brackets: <span style="font-weight: bold;" class="mycode_b">[::1]</span> is parsed and validated as an address; without brackets it's taken as a host name.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">⚙️ Por dentro: o SDK e o que ele destravou</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Under the hood</span></span><br />
<br />
🇧🇷 O plugin roda sobre o <a href="https://github.com/NullSablex/rust-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">rust-samp</span></a>, o SDK em Rust para SA-MP e open.mp, que aqui vai para a <span style="font-weight: bold;" class="mycode_b">3.6.0</span> e sai de uma tag do git para o crates.io. A mudança já conserta uma esquisitice: a tag <span style="font-weight: bold;" class="mycode_b">v3.5.0</span> declarava <span style="font-weight: bold;" class="mycode_b">version = "3.4.0"</span> no próprio manifesto, então o lockfile registrava 3.4.0 apontando para a tag v3.5.0.<br />
<br />
🇧🇷 O que a 3.6.0 trouxe de útil aqui foi uma rota que faltava. Quando uma query termina numa worker thread, o resultado precisa aterrissar no estado do plugin — e até a 3.5.0 não havia caminho do SDK para isso, limitação que o plugin contornava com canal próprio. O <span style="font-weight: bold;" class="mycode_b">post_with</span> entrega o plugin ao job, e com ele os canais de resultado do plugin deixaram de existir, tanto na query quanto no hash de senha.<br />
<br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">Não é ganho de desempenho, e eu não vou vender como se fosse.</span> O SDK drena essa fila imediatamente antes do <span style="font-weight: bold;" class="mycode_b">on_tick</span>, então o despacho continua acontecendo no <span style="font-weight: bold;" class="mycode_b">on_tick</span> e nada chega um tick depois do que chegava. O ganho é de arquitetura: menos código próprio fazendo o que o SDK passou a fazer, num ponto onde errar custa caro. Os ganhos de robustez que você sente nesta versão — a rajada de 9000 queries — são do plugin, não do SDK.<br />
🇬🇧 It is not a performance win and I won't sell it as one. The SDK drains that queue immediately before <span style="font-weight: bold;" class="mycode_b">on_tick</span>, so dispatch still happens there and nothing arrives a tick later than it used to. The gain is architectural. The robustness wins you feel in this release are the plugin's, not the SDK's.<br />
<br />
🇧🇷 Um detalhe de projeto que não é questão de gosto: entregar o resultado e despachar o callback tiveram de ficar em dois passos. Chamar o Pawn de dentro do job deixaria um <span style="font-weight: bold;" class="mycode_b">public</span> reentrar numa native e pegar um segundo empréstimo mutável do mesmo plugin, que é instantaneamente um bug de memória. Então o job só bufferiza, e quem invoca callback é o <span style="font-weight: bold;" class="mycode_b">on_tick</span>, que detém esse empréstimo legitimamente. A ordem FIFO continua sendo nossa, no buffer de sequência, porque os jobs terminam na ordem em que o banco responde, não na que você pediu.<br />
🇬🇧 Handing the result over and dispatching the callback had to be two steps: calling Pawn from inside the job would let a <span style="font-weight: bold;" class="mycode_b">public</span> re-enter a native and take a second mutable borrow of the plugin. The job only buffers; <span style="font-weight: bold;" class="mycode_b">on_tick</span> dispatches.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Gargalo antigo que já não é seu problema</span><br />
🇧🇷 Vindo de outro plugin MySQL você provavelmente aprendeu a chamar <span style="font-weight: bold;" class="mycode_b">mysql_tick()</span> num timer. Aqui não é necessário desde a 3.0.0 do SDK: a fila é bombeada a cada tick, sozinha. A native continua existindo só por compatibilidade — e ela não libera o resultado do <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span>, que sai no tick seguinte de todo jeito.<br />
🇬🇧 Coming from another MySQL plugin you probably learned to call <span style="font-weight: bold;" class="mycode_b">mysql_tick()</span> on a timer. Not needed here since SDK 3.0.0: the queue is pumped every tick automatically.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📖 A referência da API agora se escreve sozinha</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">The API reference generates itself</span></span><br />
<br />
🇧🇷 A <a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">referência da API</span></a> era uma tabela escrita à mão com cada native, seu tipo e uma descrição — exatamente a informação que o include já carrega ao lado de cada declaração, mantida em sincronia a dedo. Agora ela é gerada do include a cada build: natives, o forward, as constantes documentadas e os enums com seus valores, cada um com assinatura, apelido open.mp, parâmetros e retorno. A cópia manual foi apagada, e a URL é a mesma.<br />
<br />
🇧🇷 O gerador lê dois formatos de comentário: o <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> (<span style="font-weight: bold;" class="mycode_b">@param</span>, <span style="font-weight: bold;" class="mycode_b">@return</span>), que é o que estes includes usam, e o <span style="font-weight: bold;" class="mycode_b">pawndoc</span> (<span style="font-weight: bold;" class="mycode_b">&lt;summary&gt;</span>, <span style="font-weight: bold;" class="mycode_b">&lt;param name=""&gt;</span>, <span style="font-weight: bold;" class="mycode_b">&lt;returns&gt;</span>), que é o que você encontra em includes de terceiros. E tem autoteste: se uma native entrar sem bloco de documentação, o build da doc falha em vez de publicar uma linha vazia.<br />
<br />
🇧🇷 Esses mesmos comentários chegam ao hover do seu editor — na extensão <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> para VS Code, por exemplo, você passa o mouse na native e lê o que cada parâmetro espera sem abrir o <span style="font-weight: bold;" class="mycode_b">.inc</span>. Nesta versão as três natives que aceitam <span style="font-weight: bold;" class="mycode_b">MYSQL_SYNC</span> carregam o exemplo bloqueante no próprio comentário, então o jeito de fazer uma chamada esperar aparece onde a native é documentada, não só na página de queries.<br />
<br />
🇬🇧 The API reference is now generated from the includes on every docs build — natives, forward, documented constants and enums with their values. Two comment formats are read: <span style="font-weight: bold;" class="mycode_b">JavaDoc</span>, used here, and <span style="font-weight: bold;" class="mycode_b">pawndoc</span>. A native without a doc block fails the docs build. The same comments reach your editor's hover — e.g. the <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> VS Code extension.<br />
<br />
<hr class="mycode_hr" />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Prepared statement continua sendo a via segura. O <span style="font-weight: bold;" class="mycode_b">mysql_format</span> e o ORM interpolam texto e dependem do escaping; o <span style="font-weight: bold;" class="mycode_b">mysql_stmt_*</span> manda os valores pelo protocolo binário e não tem esse risco. Para qualquer coisa que carregue o que o jogador digitou, prefira statement — isso não mudou nesta versão e não vai mudar.<br />
🇬🇧 Prepared statements are still the safe path for anything carrying player input.</blockquote>
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download da 1.4.0</span></a> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">.so</span>, <span style="font-weight: bold;" class="mycode_b">.dll</span> e os dois includes / 🇬🇧 binaries and both includes<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 conexão, queries, cache, ORM, opções e segurança / 🇬🇧 connection, queries, cache, ORM, options, security<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 79 natives, geradas do include / 🇬🇧 all 79 natives, generated from the include<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/migration/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Guia de migração</span></a> — 🇧🇷 para quem vem de outro plugin MySQL / 🇬🇧 coming from another MySQL plugin<br />
</li>
<li><a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG completo</span></a> — 🇧🇷 com o que não caberia aqui / 🇬🇧 the full list<br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Projeto independente, mantido por uma pessoa. Não é afiliado, endossado nem patrocinado pelo SA-MP ou pelo open.mp. "SA-MP", "open.mp" e "MySQL" pertencem a seus respectivos donos e são citados apenas para descrever compatibilidade.<br />
🇬🇧 Independent project, maintained by one person. Not affiliated with or endorsed by SA-MP or open.mp.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/mysql_samp</span></a></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[email_samp for SA-MP and open.mp — SMTP straight from the server]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6472</link>
			<pubDate>Sat, 26 Sep 2026 07:23:38 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=7983">NullSablex</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6472</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">📧 email_samp 1.0.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">E-mail (SMTP) direto do servidor — SA-MP e open.mp, o mesmo binário</span><br />
<span style="font-style: italic;" class="mycode_i">Email (SMTP) straight from the server — SA-MP and open.mp, the same binary</span></span><br />
<br />
<a href="https://github.com/NullSablex/email-samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/email-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://email-samp.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Código de registro, recuperação de senha, aviso para a staff: sempre deu para fazer, mas nunca de dentro do servidor. Era requisição HTTP para um script PHP, PHPMailer do outro lado, um serviço externo no meio — três coisas para manter no ar só para mandar um e-mail. Este plugin fala <span style="font-weight: bold;" class="mycode_b">SMTP</span> e <span style="font-weight: bold;" class="mycode_b">TLS</span> por conta própria, e o envio cabe em uma linha.<br />
<br />
🇬🇧 Registration codes, password resets, staff alerts: always doable, never from inside the server. It meant an HTTP request to a PHP script, PHPMailer on the other end, some external service in between — three moving parts just to send one mail. This plugin speaks <span style="font-weight: bold;" class="mycode_b">SMTP</span> and <span style="font-weight: bold;" class="mycode_b">TLS</span> itself, and sending fits on one line.</blockquote>
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>email_setup();  // credenciais do .env, do ambiente ou do smtp.ini<br />
email_send_to("jogador@exemplo.com", "Bem-vindo", "Sua conta esta pronta.");</code></div></div><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">Nada trava.</span> Toda native retorna na hora: o e-mail sai em threads separadas e o resultado volta num callback, então relay lento não segura o tick.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Nothing blocks.</span> Every native returns at once: mail leaves on worker threads and the result comes back in a callback, so a slow relay never holds the tick.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>email_send_to(email, "Bem-vindo", corpo, "OnMailSent", "d", playerid);<br />
<br />
Email::OnMailSent(success, playerid)<br />
{<br />
    SendClientMessage(playerid, -1, success ? ("E-mail enviado.") : ("Falhou."));<br />
    return 1;<br />
}</code></div></div><br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📦 Instalar é copiar um arquivo / Installing is copying one file</span></span><br />
<br />
🇧🇷 Sem PHPMailer, sem <span style="font-weight: bold;" class="mycode_b">sendmail</span>, sem OpenSSL no sistema: SMTP e TLS estão <span style="font-weight: bold;" class="mycode_b">compilados dentro do binário</span>. Funciona em host compartilhado, onde você não instala nada. E é <span style="font-weight: bold;" class="mycode_b">um arquivo só</span> para os dois servidores:<br />
🇬🇧 No PHPMailer, no <span style="font-weight: bold;" class="mycode_b">sendmail</span>, no system OpenSSL: SMTP and TLS are <span style="font-weight: bold;" class="mycode_b">compiled into the binary</span>. Works on shared hosting, where you install nothing. One file, both servers:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">SA-MP</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, nome no <span style="font-weight: bold;" class="mycode_b">server.cfg</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, name in <span style="font-weight: bold;" class="mycode_b">server.cfg</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp nativo</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">components/</span>, encontrado sozinho / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">components/</span>, auto-discovered<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp legacy</span> — 🇧🇷 o mesmo arquivo em <span style="font-weight: bold;" class="mycode_b">plugins/</span> / 🇬🇧 same file in <span style="font-weight: bold;" class="mycode_b">plugins/</span><br />
</li>
</ul>
<br />
🇧🇷 Dois includes: <span style="font-weight: bold;" class="mycode_b">&lt;email_samp&gt;</span> em snake_case e <span style="font-weight: bold;" class="mycode_b">&lt;email_samp_omp&gt;</span> no estilo <span style="font-weight: bold;" class="mycode_b">Email_SendTo</span>, para quem não quer misturar convenção no gamemode.<br />
🇬🇧 Two includes: <span style="font-weight: bold;" class="mycode_b">&lt;email_samp&gt;</span> in snake_case and <span style="font-weight: bold;" class="mycode_b">&lt;email_samp_omp&gt;</span> in <span style="font-weight: bold;" class="mycode_b">Email_SendTo</span> style.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">✉️ O texto do e-mail fica num arquivo / The wording lives in a file</span></span><br />
<br />
🇧🇷 Quem já mandava e-mail pelo PHP tem o <span style="font-weight: bold;" class="mycode_b">.html</span> pronto — ele serve como está. Ou use um <span style="font-weight: bold;" class="mycode_b">.tpl</span>, que carrega assunto, versão texto e versão HTML juntos:<br />
🇬🇧 If you already sent mail from PHP, your <span style="font-weight: bold;" class="mycode_b">.html</span> works as-is. Or use a <span style="font-weight: bold;" class="mycode_b">.tpl</span>, which carries subject, text and HTML together:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>[subject]<br />
Seu codigo e {codigo}<br />
<br />
[text]<br />
Ola {nome}, seu codigo de verificacao e {codigo}.</code></div></div><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new msg = email_new();<br />
email_add_to(msg, email);<br />
email_set_template(msg, "mail/registro.tpl");<br />
email_set_var(msg, "nome", nome);<br />
email_set_var(msg, "codigo", "%d", codigo);<br />
email_send(msg);</code></div></div><br />
🇧🇷 Editou o arquivo, o próximo e-mail já sai com o texto novo — sem recompilar, sem reiniciar. E o valor que veio do jogador é <span style="font-weight: bold;" class="mycode_b">escapado sozinho</span> no HTML: nick com <span style="font-weight: bold;" class="mycode_b">&lt;script&gt;</span> chega como texto.<br />
🇬🇧 Edit the file and the next mail uses it — no recompile, no restart. Player-supplied values are <span style="font-weight: bold;" class="mycode_b">escaped automatically</span> in the HTML part.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔧 E ainda / And also</span></span><br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Anexo e imagem embutida / 🇬🇧 Attachments and inline images</span> — 🇧🇷 arquivo, texto gerado no gamemode, ou imagem que aparece dentro do HTML / 🇬🇧 a file, data built in the gamemode, or an image shown inside the HTML<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Ritmo de envio / 🇬🇧 Paced for real providers</span> — 🇧🇷 e-mails por minuto, prioridade e nova tentativa em falha temporária: mailing para a base inteira não atrasa o reset de senha de quem está esperando / 🇬🇧 rate limit, priorities and retries, so a mailing never delays a password reset<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Seguro com o que o jogador digita / 🇬🇧 Safe with player input</span> — 🇧🇷 quebra de linha em cabeçalho é recusada (senão um nick vira um <span style="font-weight: bold;" class="mycode_b">Bcc:</span> escondido), endereço é validado de verdade e anexo não sai da pasta do servidor / 🇬🇧 header injection refused, addresses parsed, attachments sandboxed<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 TLS obrigatório / 🇬🇧 TLS required</span> — 🇧🇷 se o STARTTLS não subir, o envio falha em vez de mandar a senha em texto claro / 🇬🇧 if STARTTLS fails, the send fails instead of leaking the password<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Acentuação intacta / 🇬🇧 Accents survive</span> — 🇧🇷 nick acentuado chega certo, não como <span style="font-weight: bold;" class="mycode_b">Jo?o</span> / 🇬🇧 no more <span style="font-weight: bold;" class="mycode_b">Jo?o</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Modo de teste / 🇬🇧 Dry-run</span> — 🇧🇷 escreve cada mensagem como <span style="font-weight: bold;" class="mycode_b">.eml</span> em <span style="font-weight: bold;" class="mycode_b">logs/dry-run/</span> e não conecta: dá para mexer no gamemode sem gastar cota nem mandar e-mail para jogador de verdade / 🇬🇧 writes each message as an <span style="font-weight: bold;" class="mycode_b">.eml</span> and sends nothing<br />
</li>
</ul>
<br />
🇧🇷 Credenciais ficam fora do código: um <span style="font-weight: bold;" class="mycode_b">.env</span> (via <a href="https://github.com/NullSablex/env-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">env_samp</span></a>), variáveis de ambiente, uma URL SMTP ou um <span style="font-weight: bold;" class="mycode_b">smtp.ini</span> — as mesmas chaves em qualquer fonte, e nenhuma delas precisa ir para o repositório.<br />
🇬🇧 Credentials stay out of the source: a <span style="font-weight: bold;" class="mycode_b">.env</span>, the process environment, an SMTP URL or an <span style="font-weight: bold;" class="mycode_b">smtp.ini</span> — the same keys from any source, and none of them has to reach your repo.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://email-samp.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 instalação, configuração, envio, templates, erros e segurança / 🇬🇧 installation, configuration, sending, templates, errors, security<br />
</li>
<li><a href="https://email-samp.nullsablex.com/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 34 natives, uma a uma / 🇬🇧 all 34 natives<br />
</li>
<li><a href="https://github.com/NullSablex/email-samp/tree/master/examples" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Exemplos</span></a> — 🇧🇷 comece pelo <span style="font-weight: bold;" class="mycode_b">00_ready_to_send</span>: um gamemode pronto, com o arquivo do e-mail junto / 🇬🇧 start with <span style="font-weight: bold;" class="mycode_b">00_ready_to_send</span><br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Projeto independente, mantido por uma pessoa. <span style="font-weight: bold;" class="mycode_b">Não</span> é afiliado, endossado nem patrocinado pelo SA-MP, pelo open.mp ou por qualquer provedor de e-mail citado.<br />
🇬🇧 Independent project, maintained by one person. <span style="font-weight: bold;" class="mycode_b">Not</span> affiliated with or endorsed by SA-MP, open.mp or any email provider mentioned.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/email-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/email-samp</span></a></div>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">📧 email_samp 1.0.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">E-mail (SMTP) direto do servidor — SA-MP e open.mp, o mesmo binário</span><br />
<span style="font-style: italic;" class="mycode_i">Email (SMTP) straight from the server — SA-MP and open.mp, the same binary</span></span><br />
<br />
<a href="https://github.com/NullSablex/email-samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/email-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://email-samp.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Código de registro, recuperação de senha, aviso para a staff: sempre deu para fazer, mas nunca de dentro do servidor. Era requisição HTTP para um script PHP, PHPMailer do outro lado, um serviço externo no meio — três coisas para manter no ar só para mandar um e-mail. Este plugin fala <span style="font-weight: bold;" class="mycode_b">SMTP</span> e <span style="font-weight: bold;" class="mycode_b">TLS</span> por conta própria, e o envio cabe em uma linha.<br />
<br />
🇬🇧 Registration codes, password resets, staff alerts: always doable, never from inside the server. It meant an HTTP request to a PHP script, PHPMailer on the other end, some external service in between — three moving parts just to send one mail. This plugin speaks <span style="font-weight: bold;" class="mycode_b">SMTP</span> and <span style="font-weight: bold;" class="mycode_b">TLS</span> itself, and sending fits on one line.</blockquote>
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>email_setup();  // credenciais do .env, do ambiente ou do smtp.ini<br />
email_send_to("jogador@exemplo.com", "Bem-vindo", "Sua conta esta pronta.");</code></div></div><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">Nada trava.</span> Toda native retorna na hora: o e-mail sai em threads separadas e o resultado volta num callback, então relay lento não segura o tick.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Nothing blocks.</span> Every native returns at once: mail leaves on worker threads and the result comes back in a callback, so a slow relay never holds the tick.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>email_send_to(email, "Bem-vindo", corpo, "OnMailSent", "d", playerid);<br />
<br />
Email::OnMailSent(success, playerid)<br />
{<br />
    SendClientMessage(playerid, -1, success ? ("E-mail enviado.") : ("Falhou."));<br />
    return 1;<br />
}</code></div></div><br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📦 Instalar é copiar um arquivo / Installing is copying one file</span></span><br />
<br />
🇧🇷 Sem PHPMailer, sem <span style="font-weight: bold;" class="mycode_b">sendmail</span>, sem OpenSSL no sistema: SMTP e TLS estão <span style="font-weight: bold;" class="mycode_b">compilados dentro do binário</span>. Funciona em host compartilhado, onde você não instala nada. E é <span style="font-weight: bold;" class="mycode_b">um arquivo só</span> para os dois servidores:<br />
🇬🇧 No PHPMailer, no <span style="font-weight: bold;" class="mycode_b">sendmail</span>, no system OpenSSL: SMTP and TLS are <span style="font-weight: bold;" class="mycode_b">compiled into the binary</span>. Works on shared hosting, where you install nothing. One file, both servers:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">SA-MP</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, nome no <span style="font-weight: bold;" class="mycode_b">server.cfg</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, name in <span style="font-weight: bold;" class="mycode_b">server.cfg</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp nativo</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">components/</span>, encontrado sozinho / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">components/</span>, auto-discovered<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp legacy</span> — 🇧🇷 o mesmo arquivo em <span style="font-weight: bold;" class="mycode_b">plugins/</span> / 🇬🇧 same file in <span style="font-weight: bold;" class="mycode_b">plugins/</span><br />
</li>
</ul>
<br />
🇧🇷 Dois includes: <span style="font-weight: bold;" class="mycode_b">&lt;email_samp&gt;</span> em snake_case e <span style="font-weight: bold;" class="mycode_b">&lt;email_samp_omp&gt;</span> no estilo <span style="font-weight: bold;" class="mycode_b">Email_SendTo</span>, para quem não quer misturar convenção no gamemode.<br />
🇬🇧 Two includes: <span style="font-weight: bold;" class="mycode_b">&lt;email_samp&gt;</span> in snake_case and <span style="font-weight: bold;" class="mycode_b">&lt;email_samp_omp&gt;</span> in <span style="font-weight: bold;" class="mycode_b">Email_SendTo</span> style.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">✉️ O texto do e-mail fica num arquivo / The wording lives in a file</span></span><br />
<br />
🇧🇷 Quem já mandava e-mail pelo PHP tem o <span style="font-weight: bold;" class="mycode_b">.html</span> pronto — ele serve como está. Ou use um <span style="font-weight: bold;" class="mycode_b">.tpl</span>, que carrega assunto, versão texto e versão HTML juntos:<br />
🇬🇧 If you already sent mail from PHP, your <span style="font-weight: bold;" class="mycode_b">.html</span> works as-is. Or use a <span style="font-weight: bold;" class="mycode_b">.tpl</span>, which carries subject, text and HTML together:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>[subject]<br />
Seu codigo e {codigo}<br />
<br />
[text]<br />
Ola {nome}, seu codigo de verificacao e {codigo}.</code></div></div><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new msg = email_new();<br />
email_add_to(msg, email);<br />
email_set_template(msg, "mail/registro.tpl");<br />
email_set_var(msg, "nome", nome);<br />
email_set_var(msg, "codigo", "%d", codigo);<br />
email_send(msg);</code></div></div><br />
🇧🇷 Editou o arquivo, o próximo e-mail já sai com o texto novo — sem recompilar, sem reiniciar. E o valor que veio do jogador é <span style="font-weight: bold;" class="mycode_b">escapado sozinho</span> no HTML: nick com <span style="font-weight: bold;" class="mycode_b">&lt;script&gt;</span> chega como texto.<br />
🇬🇧 Edit the file and the next mail uses it — no recompile, no restart. Player-supplied values are <span style="font-weight: bold;" class="mycode_b">escaped automatically</span> in the HTML part.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔧 E ainda / And also</span></span><br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Anexo e imagem embutida / 🇬🇧 Attachments and inline images</span> — 🇧🇷 arquivo, texto gerado no gamemode, ou imagem que aparece dentro do HTML / 🇬🇧 a file, data built in the gamemode, or an image shown inside the HTML<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Ritmo de envio / 🇬🇧 Paced for real providers</span> — 🇧🇷 e-mails por minuto, prioridade e nova tentativa em falha temporária: mailing para a base inteira não atrasa o reset de senha de quem está esperando / 🇬🇧 rate limit, priorities and retries, so a mailing never delays a password reset<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Seguro com o que o jogador digita / 🇬🇧 Safe with player input</span> — 🇧🇷 quebra de linha em cabeçalho é recusada (senão um nick vira um <span style="font-weight: bold;" class="mycode_b">Bcc:</span> escondido), endereço é validado de verdade e anexo não sai da pasta do servidor / 🇬🇧 header injection refused, addresses parsed, attachments sandboxed<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 TLS obrigatório / 🇬🇧 TLS required</span> — 🇧🇷 se o STARTTLS não subir, o envio falha em vez de mandar a senha em texto claro / 🇬🇧 if STARTTLS fails, the send fails instead of leaking the password<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Acentuação intacta / 🇬🇧 Accents survive</span> — 🇧🇷 nick acentuado chega certo, não como <span style="font-weight: bold;" class="mycode_b">Jo?o</span> / 🇬🇧 no more <span style="font-weight: bold;" class="mycode_b">Jo?o</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Modo de teste / 🇬🇧 Dry-run</span> — 🇧🇷 escreve cada mensagem como <span style="font-weight: bold;" class="mycode_b">.eml</span> em <span style="font-weight: bold;" class="mycode_b">logs/dry-run/</span> e não conecta: dá para mexer no gamemode sem gastar cota nem mandar e-mail para jogador de verdade / 🇬🇧 writes each message as an <span style="font-weight: bold;" class="mycode_b">.eml</span> and sends nothing<br />
</li>
</ul>
<br />
🇧🇷 Credenciais ficam fora do código: um <span style="font-weight: bold;" class="mycode_b">.env</span> (via <a href="https://github.com/NullSablex/env-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">env_samp</span></a>), variáveis de ambiente, uma URL SMTP ou um <span style="font-weight: bold;" class="mycode_b">smtp.ini</span> — as mesmas chaves em qualquer fonte, e nenhuma delas precisa ir para o repositório.<br />
🇬🇧 Credentials stay out of the source: a <span style="font-weight: bold;" class="mycode_b">.env</span>, the process environment, an SMTP URL or an <span style="font-weight: bold;" class="mycode_b">smtp.ini</span> — the same keys from any source, and none of them has to reach your repo.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://email-samp.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 instalação, configuração, envio, templates, erros e segurança / 🇬🇧 installation, configuration, sending, templates, errors, security<br />
</li>
<li><a href="https://email-samp.nullsablex.com/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 34 natives, uma a uma / 🇬🇧 all 34 natives<br />
</li>
<li><a href="https://github.com/NullSablex/email-samp/tree/master/examples" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Exemplos</span></a> — 🇧🇷 comece pelo <span style="font-weight: bold;" class="mycode_b">00_ready_to_send</span>: um gamemode pronto, com o arquivo do e-mail junto / 🇬🇧 start with <span style="font-weight: bold;" class="mycode_b">00_ready_to_send</span><br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Projeto independente, mantido por uma pessoa. <span style="font-weight: bold;" class="mycode_b">Não</span> é afiliado, endossado nem patrocinado pelo SA-MP, pelo open.mp ou por qualquer provedor de e-mail citado.<br />
🇬🇧 Independent project, maintained by one person. <span style="font-weight: bold;" class="mycode_b">Not</span> affiliated with or endorsed by SA-MP, open.mp or any email provider mentioned.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/email-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/email-samp</span></a></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[OxTarget - Native 3D Targeting & Interaction System for SA-MP/open.mp]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6385</link>
			<pubDate>Tue, 08 Sep 2026 14:07:01 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8418">RoseeasT</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6385</guid>
			<description><![CDATA[OxTarget is an out-of-the-box C++17 targeting and interaction engine for SA-MP 0.3.7 / 0.3.DL and open.mp.<br />
<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/zaNo_1ruYW0" frameborder="0" allowfullscreen="true"></iframe><br />
<br />
It enables players to interact with entities by simply looking at them via the camera in a way similar to the one used in modern targeting/interactions available in contemporary multiplayer engines.<br />
<br />
The plugin takes care of all the complex 3D targeting computations natively, but provides an easy-to-use Pawn API for scripting purposes.<br />
<br />
Features<br />
Camera-based 3D targeting of entities<br />
Players, actors, vehicles, objects, player objects, dynamic objects, pickups, and custom points<br />
Oriented Bounding Box (OBB), Capsule, and Sphere collision checks<br />
Vehicle/Entity sub-object detection such as:<br />
Front / Hood<br />
Rear / Trunk<br />
Left / Right<br />
Doors<br />
Top / Bottom<br />
Optional zoning of interactions<br />
Spatial hash map-based fast target finding<br />
Server-side distance check, world check, interior check, and target check<br />
Builtin interactive PlayerTextDraw UI<br />
PC Mouse and Android Touch support<br />
Hotkeys for interacting via keyboard<br />
Standalone camera/world raycasting API<br />
Optional compatibility with the Streamer Plugin<br />
Optional compatibility with the ColAndreas plugin for world occlusion support<br />
Custom mode with your own TextDraw, dialogs, radial menus, or CEF UI<br />
Windows &amp; Linux<br />
x86 and x64 versions<br />
SA-MP and open.mp compatibility<br />
Client-side mods are not needed<br />
<br />
The goal of OxTarget is not to make something that would be completely impossible in Pawn. A simpler targeting system could obviously be implemented there.<br />
The idea is to move the geometry, ray intersection, spatial querying, and validation layer into a reusable native engine, while keeping the actual gameplay logic inside Pawn.<br />
Repository:<br />
<a href="https://github.com/roseeast/OxTarget" target="_blank" rel="noopener" class="mycode_url">https://github.com/roseeast/OxTarget</a><br />
Feedback, bug reports, suggestions, and contributions are welcome.]]></description>
			<content:encoded><![CDATA[OxTarget is an out-of-the-box C++17 targeting and interaction engine for SA-MP 0.3.7 / 0.3.DL and open.mp.<br />
<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/zaNo_1ruYW0" frameborder="0" allowfullscreen="true"></iframe><br />
<br />
It enables players to interact with entities by simply looking at them via the camera in a way similar to the one used in modern targeting/interactions available in contemporary multiplayer engines.<br />
<br />
The plugin takes care of all the complex 3D targeting computations natively, but provides an easy-to-use Pawn API for scripting purposes.<br />
<br />
Features<br />
Camera-based 3D targeting of entities<br />
Players, actors, vehicles, objects, player objects, dynamic objects, pickups, and custom points<br />
Oriented Bounding Box (OBB), Capsule, and Sphere collision checks<br />
Vehicle/Entity sub-object detection such as:<br />
Front / Hood<br />
Rear / Trunk<br />
Left / Right<br />
Doors<br />
Top / Bottom<br />
Optional zoning of interactions<br />
Spatial hash map-based fast target finding<br />
Server-side distance check, world check, interior check, and target check<br />
Builtin interactive PlayerTextDraw UI<br />
PC Mouse and Android Touch support<br />
Hotkeys for interacting via keyboard<br />
Standalone camera/world raycasting API<br />
Optional compatibility with the Streamer Plugin<br />
Optional compatibility with the ColAndreas plugin for world occlusion support<br />
Custom mode with your own TextDraw, dialogs, radial menus, or CEF UI<br />
Windows &amp; Linux<br />
x86 and x64 versions<br />
SA-MP and open.mp compatibility<br />
Client-side mods are not needed<br />
<br />
The goal of OxTarget is not to make something that would be completely impossible in Pawn. A simpler targeting system could obviously be implemented there.<br />
The idea is to move the geometry, ray intersection, spatial querying, and validation layer into a reusable native engine, while keeping the actual gameplay logic inside Pawn.<br />
Repository:<br />
<a href="https://github.com/roseeast/OxTarget" target="_blank" rel="noopener" class="mycode_url">https://github.com/roseeast/OxTarget</a><br />
Feedback, bug reports, suggestions, and contributions are welcome.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[mysql_samp — MySQL support for SA-MP and open.mp, now featuring an open.mp-style API]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6368</link>
			<pubDate>Mon, 07 Sep 2026 10:02:43 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=7983">NullSablex</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6368</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">🦀 mysql_samp 1.3.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">MySQL para SA-MP e open.mp — o mesmo binário nos dois</span><br />
<span style="font-style: italic;" class="mycode_i">MySQL for SA-MP and open.mp — the same binary on both</span></span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Um plugin MySQL escrito em <span style="font-weight: bold;" class="mycode_b">Rust</span>, com <span style="font-weight: bold;" class="mycode_b">75 natives</span> e <span style="font-weight: bold;" class="mycode_b">nenhuma dependência para instalar</span>. O novo desta versão: a API inteira também está disponível na convenção de nomes do <span style="font-weight: bold;" class="mycode_b">open.mp</span> — <span style="font-weight: bold;" class="mycode_b">MySQL_Connect</span>, <span style="font-weight: bold;" class="mycode_b">Cache_GetRowCount</span>, <span style="font-weight: bold;" class="mycode_b">ORM_Create</span> — para quem não quer <span style="font-weight: bold;" class="mycode_b">snake_case</span> no meio do gamemode.<br />
<br />
🇬🇧 A MySQL plugin written in <span style="font-weight: bold;" class="mycode_b">Rust</span>, with <span style="font-weight: bold;" class="mycode_b">75 natives</span> and <span style="font-weight: bold;" class="mycode_b">nothing to install alongside it</span>. New in this release: the whole API is also available in <span style="font-weight: bold;" class="mycode_b">open.mp</span>'s naming convention — for anyone who doesn't want <span style="font-weight: bold;" class="mycode_b">snake_case</span> in the middle of their gamemode.</blockquote>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📦 Instalar é copiar um arquivo / Installing is copying one file</span></span><br />
<br />
🇧🇷 Não há <span style="font-weight: bold;" class="mycode_b">libmysqlclient</span> para caçar na versão certa, nem <span style="font-weight: bold;" class="mycode_b">Boost</span>, nem <span style="font-weight: bold;" class="mycode_b">OpenSSL</span> no sistema. O protocolo do MySQL e o TLS estão <span style="font-weight: bold;" class="mycode_b">compilados dentro do binário</span>. Você joga o <span style="font-weight: bold;" class="mycode_b">.so</span> ou o <span style="font-weight: bold;" class="mycode_b">.dll</span> na pasta e acabou — inclusive em host compartilhado, onde você não instala pacote nenhum.<br />
<br />
🇬🇧 No <span style="font-weight: bold;" class="mycode_b">libmysqlclient</span> to hunt down, no <span style="font-weight: bold;" class="mycode_b">Boost</span>, no system <span style="font-weight: bold;" class="mycode_b">OpenSSL</span>. The MySQL protocol and TLS are <span style="font-weight: bold;" class="mycode_b">compiled into the binary</span>. Drop the <span style="font-weight: bold;" class="mycode_b">.so</span> or <span style="font-weight: bold;" class="mycode_b">.dll</span> in and you're done — including on shared hosting, where you install nothing.<br />
<br />
🇧🇷 E é <span style="font-weight: bold;" class="mycode_b">um binário só</span> para os dois servidores:<br />
🇬🇧 And it's <span style="font-weight: bold;" class="mycode_b">one binary</span> for both servers:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">SA-MP</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, nome no <span style="font-weight: bold;" class="mycode_b">server.cfg</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, name in <span style="font-weight: bold;" class="mycode_b">server.cfg</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp nativo</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">components/</span>. É encontrado sozinho, <span style="font-weight: bold;" class="mycode_b">sem mexer no config.json</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">components/</span>. Auto-discovered, <span style="font-weight: bold;" class="mycode_b">no config.json entry</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp legacy</span> — 🇧🇷 o mesmo arquivo em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, declarado em <span style="font-weight: bold;" class="mycode_b">legacy_plugins</span> / 🇬🇧 same file in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, listed under <span style="font-weight: bold;" class="mycode_b">legacy_plugins</span><br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🆕 A novidade da 1.3.0: a API no estilo open.mp</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">New in 1.3.0: the API in open.mp style</span></span><br />
<br />
🇧🇷 Quem desenvolve para open.mp escreve <span style="font-weight: bold;" class="mycode_b">CreateVehicle</span>, <span style="font-weight: bold;" class="mycode_b">GetPlayerName</span>, <span style="font-weight: bold;" class="mycode_b">TogglePlayerControllable</span>. Aí chega a parte do banco de dados e o código vira <span style="font-weight: bold;" class="mycode_b">mysql_connect</span>, <span style="font-weight: bold;" class="mycode_b">cache_get_row_count</span>. Funciona, mas destoa.<br />
<br />
🇬🇧 If you develop for open.mp you write <span style="font-weight: bold;" class="mycode_b">CreateVehicle</span>, <span style="font-weight: bold;" class="mycode_b">GetPlayerName</span>. Then the database part arrives and the code turns into <span style="font-weight: bold;" class="mycode_b">mysql_connect</span>, <span style="font-weight: bold;" class="mycode_b">cache_get_row_count</span>. It works, but it clashes.<br />
<br />
🇧🇷 A partir desta versão existe um segundo include com <span style="font-weight: bold;" class="mycode_b">todas as 75 natives</span> na convenção <span style="font-weight: bold;" class="mycode_b">Prefixo_PascalCase</span>:<br />
🇬🇧 From this release there's a second include with <span style="font-weight: bold;" class="mycode_b">all 75 natives</span> in <span style="font-weight: bold;" class="mycode_b">Prefix_PascalCase</span>:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>// mysql_samp.inc                    // mysql_samp_omp.inc<br />
mysql_connect(...)                  MySQL_Connect(...)<br />
mysql_query(...)                    MySQL_Query(...)<br />
cache_get_row_count()                Cache_GetRowCount()<br />
orm_create(...)                      ORM_Create(...)<br />
mysql_hash_password(...)            MySQL_HashPassword(...)</code></div></div><br />
🇧🇷 São <span style="font-weight: bold;" class="mycode_b">apelidos de Pawn</span> (<span style="font-weight: bold;" class="mycode_b">native MySQL_Connect(...) = mysql_connect;</span>) — <span style="font-weight: bold;" class="mycode_b">nenhum custo em tempo de execução</span>, nada muda no plugin, é o mesmo binário. O include é gerado a partir do outro a cada build, então os dois nunca ficam fora de sincronia: native nova aparece nos dois automaticamente.<br />
<br />
🇬🇧 They're <span style="font-weight: bold;" class="mycode_b">Pawn aliases</span> — <span style="font-weight: bold;" class="mycode_b">zero runtime cost</span>, nothing changes plugin-side, same binary. The include is generated from the other one on every build, so the two can never drift.<br />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Os dois includes são <span style="font-weight: bold;" class="mycode_b">alternativas, não camadas</span>: escolha um por script. Quem já usa o <span style="font-weight: bold;" class="mycode_b">&lt;mysql_samp&gt;</span> não precisa mudar nada — nenhuma native foi removida ou renomeada.<br />
🇬🇧 The two includes are <span style="font-weight: bold;" class="mycode_b">alternatives, not layers</span>: pick one per script. Existing code needs no change — nothing was removed or renamed.</blockquote>
<br />
<span style="font-weight: bold;" class="mycode_b">📖 Documentação no editor / Docs in the editor</span><br />
🇧🇷 Junto com isso, as 75 natives ganharam bloco <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> (<span style="font-weight: bold;" class="mycode_b">@param</span>, <span style="font-weight: bold;" class="mycode_b">@return</span>) direto no include. Num editor que renderize isso — como a extensão <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> para VS Code — você passa o mouse sobre a função e lê a descrição e o que cada parâmetro espera, sem abrir o <span style="font-weight: bold;" class="mycode_b">.inc</span> para conferir a ordem dos argumentos. Em editor que não renderize, o comentário continua lá, legível como texto.<br />
🇬🇧 The 75 natives also got <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> blocks in the include. In an editor that renders them — such as the <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> extension for VS Code — hover a function and read the description and what each parameter expects. In editors that don't, it's still readable as a comment.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔧 O que o plugin faz / What the plugin does</span></span><br />
<br />
🇧🇷 Para quem está chegando agora, o conjunto completo:<br />
🇬🇧 For anyone arriving now, the full set:<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Nenhuma query trava o servidor / 🇬🇧 No query stalls the server</span><br />
🇧🇷 Não existe versão síncrona na API. <span style="font-weight: bold;" class="mycode_b">mysql_query</span> roda em thread separada e entrega os callbacks <span style="font-weight: bold;" class="mycode_b">na ordem em que você pediu</span>; <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> entrega assim que cada uma termina, sem ordem garantida.<br />
🇬🇧 There is no synchronous version in the API. <span style="font-weight: bold;" class="mycode_b">mysql_query</span> runs off-thread and delivers callbacks <span style="font-weight: bold;" class="mycode_b">in submission order</span>; <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> delivers as each finishes.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT id, nome FROM contas LIMIT 10", "OnContasCarregadas", "");<br />
<br />
forward OnContasCarregadas();<br />
public OnContasCarregadas()<br />
{<br />
    new nome[MAX_PLAYER_NAME];<br />
    for (new i = 0, rows = cache_get_row_count(); i &lt; rows; i++)<br />
    {<br />
        cache_get_value_name(i, "nome", nome);<br />
        printf("conta: %s", nome);<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 ORM: a tabela vira variável / 🇬🇧 ORM: the table becomes a variable</span><br />
🇧🇷 Você registra as variáveis do gamemode como colunas e usa <span style="font-weight: bold;" class="mycode_b">orm_select</span> / <span style="font-weight: bold;" class="mycode_b">orm_save</span>. Sem escrever <span style="font-weight: bold;" class="mycode_b">SELECT</span> e <span style="font-weight: bold;" class="mycode_b">UPDATE</span> à mão para cada campo de conta.<br />
🇬🇧 Register your gamemode variables as columns, then use <span style="font-weight: bold;" class="mycode_b">orm_select</span> / <span style="font-weight: bold;" class="mycode_b">orm_save</span>.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new ormId = orm_create("contas", g_mysql);<br />
orm_addvar_int(ormId, gPlayerId[playerid], "id");<br />
orm_addvar_int(ormId, gDinheiro[playerid], "dinheiro");<br />
orm_addvar_string(ormId, gNome[playerid], MAX_PLAYER_NAME, "nome");<br />
orm_setkey(ormId, "nome");<br />
<br />
orm_select(ormId, "OnContaCarregada", "d", playerid);  // preenche as variáveis<br />
// ... e no logout:<br />
orm_save(ormId);                                      // INSERT ou UPDATE sozinho</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Senha de jogador com Argon2id / 🇬🇧 Player passwords with Argon2id</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_hash_password</span> e <span style="font-weight: bold;" class="mycode_b">mysql_verify_password</span> rodam fora da thread do servidor — hash forte não trava o tick. O texto puro nunca entra numa query, então nunca entra num log.<br />
🇬🇧 Both run off the server thread — strong hashing doesn't stall the tick. The plaintext never reaches SQL, so it never reaches a log.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_hash_password(senhaDigitada, "OnSenhaGerada", "d", playerid);<br />
<br />
forward OnSenhaGerada(const hash[], playerid);<br />
public OnSenhaGerada(const hash[], playerid)  // guarde este hash no banco</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Prepared statements / 🇬🇧 Prepared statements</span><br />
🇧🇷 Para qualquer coisa digitada pelo jogador. Os valores vão pelo protocolo binário do MySQL: não há string montada, então não há escape para errar.<br />
🇬🇧 For anything a player typed. Values go over MySQL's binary protocol: no string is assembled, so there's no escaping to get wrong.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new stmt = mysql_stmt_new(g_mysql, "SELECT id FROM contas WHERE nome = ?");<br />
mysql_stmt_bind_str(stmt, nomeDigitado);<br />
mysql_stmt_execute(stmt, "OnContaEncontrada", "d", playerid);</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ainda / 🇬🇧 And also</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Transações / 🇬🇧 Transactions</span> — 🇧🇷 lote atômico: ou todos os passos entram, ou nenhum entra / 🇬🇧 atomic batch: all steps or none<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Cache de resultados / 🇬🇧 Result cache</span> — 🇧🇷 pilha automática dentro do callback, ou guardado à mão com <span style="font-weight: bold;" class="mycode_b">cache_save</span>, inclusive múltiplos resultados de <span style="font-weight: bold;" class="mycode_b">stored procedure</span> / 🇬🇧 automatic stack, or kept manually with <span style="font-weight: bold;" class="mycode_b">cache_save</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Credenciais fora do código / 🇬🇧 Credentials out of the source</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_connect_file</span> lê de um <span style="font-weight: bold;" class="mycode_b">.ini</span> que seu repositório não precisa carregar / 🇬🇧 read from an <span style="font-weight: bold;" class="mycode_b">.ini</span> your repo doesn't have to carry<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Scripts .sql / 🇬🇧 .sql scripts</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> executa o arquivo inteiro, em ordem, sem bloquear / 🇬🇧 runs the whole file, in order, non-blocking<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 TLS embutido / 🇬🇧 Built-in TLS</span> — 🇧🇷 com CA fixa, TLS mútuo e verificação de certificado ligada por padrão / 🇬🇧 CA pinning, mTLS, verification on by default<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Log próprio / 🇬🇧 Its own log</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">logs/mysql.log</span> com o detalhe do erro; no console fica só a mensagem curta, sem dado sensível / 🇬🇧 full detail in the file, short message on the console<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎯 Começando / Getting started</span></span><br />
<br />
🇧🇷 Baixe o binário da sua plataforma e o include que preferir — os dois são <span style="font-weight: bold;" class="mycode_b">32 bits</span>, como o servidor. Depois:<br />
🇬🇧 Download the binary for your platform and the include you prefer — both are <span style="font-weight: bold;" class="mycode_b">32-bit</span>, like the server. Then:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;a_samp&gt;<br />
#include &lt;mysql_samp&gt;        // ou &lt;mysql_samp_omp&gt;, no estilo open.mp<br />
<br />
new g_mysql;<br />
<br />
public OnGameModeInit()<br />
{<br />
    g_mysql = mysql_connect("127.0.0.1", "root", "senha", "meu_banco");<br />
<br />
    if (mysql_errno() != MYSQL_OK)<br />
    {<br />
        print("[MySQL] falha ao conectar - veja logs/mysql.log");<br />
        return 1;<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
🇧🇷 O passo a passo por servidor, os caminhos de include e o que fazer quando algo não conecta estão na <a href="https://nullsablex.github.io/mysql_samp/installation/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">página de instalação</span></a>.<br />
🇬🇧 Per-server steps, include paths and troubleshooting are on the <a href="https://nullsablex.github.io/mysql_samp/installation/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">installation page</span></a>.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 conexão, queries, cache, ORM, opções e segurança / 🇬🇧 connection, queries, cache, ORM, options, security<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 75 natives, uma a uma / 🇬🇧 all 75 natives<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/migration/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Guia de migração</span></a> — 🇧🇷 para quem vem de outro plugin MySQL / 🇬🇧 coming from another MySQL plugin<br />
</li>
<li><a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG</span></a> — 🇧🇷 o que mudou em cada versão / 🇬🇧 what changed in each release<br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Este projeto <span style="font-weight: bold;" class="mycode_b">não</span> é afiliado, endossado nem patrocinado pelo SA-MP ou pelo open.mp. "SA-MP", "open.mp" e "MySQL" pertencem a seus respectivos donos e são citados apenas para descrever compatibilidade.<br />
🇬🇧 This project is <span style="font-weight: bold;" class="mycode_b">not</span> affiliated with, endorsed by, or sponsored by SA-MP or open.mp. "SA-MP", "open.mp", and "MySQL" are trademarks of their respective owners and are mentioned solely to describe compatibility.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/mysql_samp</span></a></div>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #0e639c;" class="mycode_color">🦀 mysql_samp 1.3.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">MySQL para SA-MP e open.mp — o mesmo binário nos dois</span><br />
<span style="font-style: italic;" class="mycode_i">MySQL for SA-MP and open.mp — the same binary on both</span></span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp/releases/latest" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Download</span></a> · <a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">GitHub</span></a> · <a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Um plugin MySQL escrito em <span style="font-weight: bold;" class="mycode_b">Rust</span>, com <span style="font-weight: bold;" class="mycode_b">75 natives</span> e <span style="font-weight: bold;" class="mycode_b">nenhuma dependência para instalar</span>. O novo desta versão: a API inteira também está disponível na convenção de nomes do <span style="font-weight: bold;" class="mycode_b">open.mp</span> — <span style="font-weight: bold;" class="mycode_b">MySQL_Connect</span>, <span style="font-weight: bold;" class="mycode_b">Cache_GetRowCount</span>, <span style="font-weight: bold;" class="mycode_b">ORM_Create</span> — para quem não quer <span style="font-weight: bold;" class="mycode_b">snake_case</span> no meio do gamemode.<br />
<br />
🇬🇧 A MySQL plugin written in <span style="font-weight: bold;" class="mycode_b">Rust</span>, with <span style="font-weight: bold;" class="mycode_b">75 natives</span> and <span style="font-weight: bold;" class="mycode_b">nothing to install alongside it</span>. New in this release: the whole API is also available in <span style="font-weight: bold;" class="mycode_b">open.mp</span>'s naming convention — for anyone who doesn't want <span style="font-weight: bold;" class="mycode_b">snake_case</span> in the middle of their gamemode.</blockquote>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📦 Instalar é copiar um arquivo / Installing is copying one file</span></span><br />
<br />
🇧🇷 Não há <span style="font-weight: bold;" class="mycode_b">libmysqlclient</span> para caçar na versão certa, nem <span style="font-weight: bold;" class="mycode_b">Boost</span>, nem <span style="font-weight: bold;" class="mycode_b">OpenSSL</span> no sistema. O protocolo do MySQL e o TLS estão <span style="font-weight: bold;" class="mycode_b">compilados dentro do binário</span>. Você joga o <span style="font-weight: bold;" class="mycode_b">.so</span> ou o <span style="font-weight: bold;" class="mycode_b">.dll</span> na pasta e acabou — inclusive em host compartilhado, onde você não instala pacote nenhum.<br />
<br />
🇬🇧 No <span style="font-weight: bold;" class="mycode_b">libmysqlclient</span> to hunt down, no <span style="font-weight: bold;" class="mycode_b">Boost</span>, no system <span style="font-weight: bold;" class="mycode_b">OpenSSL</span>. The MySQL protocol and TLS are <span style="font-weight: bold;" class="mycode_b">compiled into the binary</span>. Drop the <span style="font-weight: bold;" class="mycode_b">.so</span> or <span style="font-weight: bold;" class="mycode_b">.dll</span> in and you're done — including on shared hosting, where you install nothing.<br />
<br />
🇧🇷 E é <span style="font-weight: bold;" class="mycode_b">um binário só</span> para os dois servidores:<br />
🇬🇧 And it's <span style="font-weight: bold;" class="mycode_b">one binary</span> for both servers:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">SA-MP</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, nome no <span style="font-weight: bold;" class="mycode_b">server.cfg</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, name in <span style="font-weight: bold;" class="mycode_b">server.cfg</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp nativo</span> — 🇧🇷 em <span style="font-weight: bold;" class="mycode_b">components/</span>. É encontrado sozinho, <span style="font-weight: bold;" class="mycode_b">sem mexer no config.json</span> / 🇬🇧 in <span style="font-weight: bold;" class="mycode_b">components/</span>. Auto-discovered, <span style="font-weight: bold;" class="mycode_b">no config.json entry</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">open.mp legacy</span> — 🇧🇷 o mesmo arquivo em <span style="font-weight: bold;" class="mycode_b">plugins/</span>, declarado em <span style="font-weight: bold;" class="mycode_b">legacy_plugins</span> / 🇬🇧 same file in <span style="font-weight: bold;" class="mycode_b">plugins/</span>, listed under <span style="font-weight: bold;" class="mycode_b">legacy_plugins</span><br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🆕 A novidade da 1.3.0: a API no estilo open.mp</span></span><br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">New in 1.3.0: the API in open.mp style</span></span><br />
<br />
🇧🇷 Quem desenvolve para open.mp escreve <span style="font-weight: bold;" class="mycode_b">CreateVehicle</span>, <span style="font-weight: bold;" class="mycode_b">GetPlayerName</span>, <span style="font-weight: bold;" class="mycode_b">TogglePlayerControllable</span>. Aí chega a parte do banco de dados e o código vira <span style="font-weight: bold;" class="mycode_b">mysql_connect</span>, <span style="font-weight: bold;" class="mycode_b">cache_get_row_count</span>. Funciona, mas destoa.<br />
<br />
🇬🇧 If you develop for open.mp you write <span style="font-weight: bold;" class="mycode_b">CreateVehicle</span>, <span style="font-weight: bold;" class="mycode_b">GetPlayerName</span>. Then the database part arrives and the code turns into <span style="font-weight: bold;" class="mycode_b">mysql_connect</span>, <span style="font-weight: bold;" class="mycode_b">cache_get_row_count</span>. It works, but it clashes.<br />
<br />
🇧🇷 A partir desta versão existe um segundo include com <span style="font-weight: bold;" class="mycode_b">todas as 75 natives</span> na convenção <span style="font-weight: bold;" class="mycode_b">Prefixo_PascalCase</span>:<br />
🇬🇧 From this release there's a second include with <span style="font-weight: bold;" class="mycode_b">all 75 natives</span> in <span style="font-weight: bold;" class="mycode_b">Prefix_PascalCase</span>:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>// mysql_samp.inc                    // mysql_samp_omp.inc<br />
mysql_connect(...)                  MySQL_Connect(...)<br />
mysql_query(...)                    MySQL_Query(...)<br />
cache_get_row_count()                Cache_GetRowCount()<br />
orm_create(...)                      ORM_Create(...)<br />
mysql_hash_password(...)            MySQL_HashPassword(...)</code></div></div><br />
🇧🇷 São <span style="font-weight: bold;" class="mycode_b">apelidos de Pawn</span> (<span style="font-weight: bold;" class="mycode_b">native MySQL_Connect(...) = mysql_connect;</span>) — <span style="font-weight: bold;" class="mycode_b">nenhum custo em tempo de execução</span>, nada muda no plugin, é o mesmo binário. O include é gerado a partir do outro a cada build, então os dois nunca ficam fora de sincronia: native nova aparece nos dois automaticamente.<br />
<br />
🇬🇧 They're <span style="font-weight: bold;" class="mycode_b">Pawn aliases</span> — <span style="font-weight: bold;" class="mycode_b">zero runtime cost</span>, nothing changes plugin-side, same binary. The include is generated from the other one on every build, so the two can never drift.<br />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Os dois includes são <span style="font-weight: bold;" class="mycode_b">alternativas, não camadas</span>: escolha um por script. Quem já usa o <span style="font-weight: bold;" class="mycode_b">&lt;mysql_samp&gt;</span> não precisa mudar nada — nenhuma native foi removida ou renomeada.<br />
🇬🇧 The two includes are <span style="font-weight: bold;" class="mycode_b">alternatives, not layers</span>: pick one per script. Existing code needs no change — nothing was removed or renamed.</blockquote>
<br />
<span style="font-weight: bold;" class="mycode_b">📖 Documentação no editor / Docs in the editor</span><br />
🇧🇷 Junto com isso, as 75 natives ganharam bloco <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> (<span style="font-weight: bold;" class="mycode_b">@param</span>, <span style="font-weight: bold;" class="mycode_b">@return</span>) direto no include. Num editor que renderize isso — como a extensão <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> para VS Code — você passa o mouse sobre a função e lê a descrição e o que cada parâmetro espera, sem abrir o <span style="font-weight: bold;" class="mycode_b">.inc</span> para conferir a ordem dos argumentos. Em editor que não renderize, o comentário continua lá, legível como texto.<br />
🇬🇧 The 75 natives also got <span style="font-weight: bold;" class="mycode_b">JavaDoc</span> blocks in the include. In an editor that renders them — such as the <a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> extension for VS Code — hover a function and read the description and what each parameter expects. In editors that don't, it's still readable as a comment.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🔧 O que o plugin faz / What the plugin does</span></span><br />
<br />
🇧🇷 Para quem está chegando agora, o conjunto completo:<br />
🇬🇧 For anyone arriving now, the full set:<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Nenhuma query trava o servidor / 🇬🇧 No query stalls the server</span><br />
🇧🇷 Não existe versão síncrona na API. <span style="font-weight: bold;" class="mycode_b">mysql_query</span> roda em thread separada e entrega os callbacks <span style="font-weight: bold;" class="mycode_b">na ordem em que você pediu</span>; <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> entrega assim que cada uma termina, sem ordem garantida.<br />
🇬🇧 There is no synchronous version in the API. <span style="font-weight: bold;" class="mycode_b">mysql_query</span> runs off-thread and delivers callbacks <span style="font-weight: bold;" class="mycode_b">in submission order</span>; <span style="font-weight: bold;" class="mycode_b">mysql_pquery</span> delivers as each finishes.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_query(g_mysql, "SELECT id, nome FROM contas LIMIT 10", "OnContasCarregadas", "");<br />
<br />
forward OnContasCarregadas();<br />
public OnContasCarregadas()<br />
{<br />
    new nome[MAX_PLAYER_NAME];<br />
    for (new i = 0, rows = cache_get_row_count(); i &lt; rows; i++)<br />
    {<br />
        cache_get_value_name(i, "nome", nome);<br />
        printf("conta: %s", nome);<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 ORM: a tabela vira variável / 🇬🇧 ORM: the table becomes a variable</span><br />
🇧🇷 Você registra as variáveis do gamemode como colunas e usa <span style="font-weight: bold;" class="mycode_b">orm_select</span> / <span style="font-weight: bold;" class="mycode_b">orm_save</span>. Sem escrever <span style="font-weight: bold;" class="mycode_b">SELECT</span> e <span style="font-weight: bold;" class="mycode_b">UPDATE</span> à mão para cada campo de conta.<br />
🇬🇧 Register your gamemode variables as columns, then use <span style="font-weight: bold;" class="mycode_b">orm_select</span> / <span style="font-weight: bold;" class="mycode_b">orm_save</span>.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new ormId = orm_create("contas", g_mysql);<br />
orm_addvar_int(ormId, gPlayerId[playerid], "id");<br />
orm_addvar_int(ormId, gDinheiro[playerid], "dinheiro");<br />
orm_addvar_string(ormId, gNome[playerid], MAX_PLAYER_NAME, "nome");<br />
orm_setkey(ormId, "nome");<br />
<br />
orm_select(ormId, "OnContaCarregada", "d", playerid);  // preenche as variáveis<br />
// ... e no logout:<br />
orm_save(ormId);                                      // INSERT ou UPDATE sozinho</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Senha de jogador com Argon2id / 🇬🇧 Player passwords with Argon2id</span><br />
🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_hash_password</span> e <span style="font-weight: bold;" class="mycode_b">mysql_verify_password</span> rodam fora da thread do servidor — hash forte não trava o tick. O texto puro nunca entra numa query, então nunca entra num log.<br />
🇬🇧 Both run off the server thread — strong hashing doesn't stall the tick. The plaintext never reaches SQL, so it never reaches a log.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>mysql_hash_password(senhaDigitada, "OnSenhaGerada", "d", playerid);<br />
<br />
forward OnSenhaGerada(const hash[], playerid);<br />
public OnSenhaGerada(const hash[], playerid)  // guarde este hash no banco</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 Prepared statements / 🇬🇧 Prepared statements</span><br />
🇧🇷 Para qualquer coisa digitada pelo jogador. Os valores vão pelo protocolo binário do MySQL: não há string montada, então não há escape para errar.<br />
🇬🇧 For anything a player typed. Values go over MySQL's binary protocol: no string is assembled, so there's no escaping to get wrong.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new stmt = mysql_stmt_new(g_mysql, "SELECT id FROM contas WHERE nome = ?");<br />
mysql_stmt_bind_str(stmt, nomeDigitado);<br />
mysql_stmt_execute(stmt, "OnContaEncontrada", "d", playerid);</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">🇧🇷 E ainda / 🇬🇧 And also</span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Transações / 🇬🇧 Transactions</span> — 🇧🇷 lote atômico: ou todos os passos entram, ou nenhum entra / 🇬🇧 atomic batch: all steps or none<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Cache de resultados / 🇬🇧 Result cache</span> — 🇧🇷 pilha automática dentro do callback, ou guardado à mão com <span style="font-weight: bold;" class="mycode_b">cache_save</span>, inclusive múltiplos resultados de <span style="font-weight: bold;" class="mycode_b">stored procedure</span> / 🇬🇧 automatic stack, or kept manually with <span style="font-weight: bold;" class="mycode_b">cache_save</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Credenciais fora do código / 🇬🇧 Credentials out of the source</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_connect_file</span> lê de um <span style="font-weight: bold;" class="mycode_b">.ini</span> que seu repositório não precisa carregar / 🇬🇧 read from an <span style="font-weight: bold;" class="mycode_b">.ini</span> your repo doesn't have to carry<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Scripts .sql / 🇬🇧 .sql scripts</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">mysql_query_file</span> executa o arquivo inteiro, em ordem, sem bloquear / 🇬🇧 runs the whole file, in order, non-blocking<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 TLS embutido / 🇬🇧 Built-in TLS</span> — 🇧🇷 com CA fixa, TLS mútuo e verificação de certificado ligada por padrão / 🇬🇧 CA pinning, mTLS, verification on by default<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">🇧🇷 Log próprio / 🇬🇧 Its own log</span> — 🇧🇷 <span style="font-weight: bold;" class="mycode_b">logs/mysql.log</span> com o detalhe do erro; no console fica só a mensagem curta, sem dado sensível / 🇬🇧 full detail in the file, short message on the console<br />
</li>
</ul>
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎯 Começando / Getting started</span></span><br />
<br />
🇧🇷 Baixe o binário da sua plataforma e o include que preferir — os dois são <span style="font-weight: bold;" class="mycode_b">32 bits</span>, como o servidor. Depois:<br />
🇬🇧 Download the binary for your platform and the include you prefer — both are <span style="font-weight: bold;" class="mycode_b">32-bit</span>, like the server. Then:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;a_samp&gt;<br />
#include &lt;mysql_samp&gt;        // ou &lt;mysql_samp_omp&gt;, no estilo open.mp<br />
<br />
new g_mysql;<br />
<br />
public OnGameModeInit()<br />
{<br />
    g_mysql = mysql_connect("127.0.0.1", "root", "senha", "meu_banco");<br />
<br />
    if (mysql_errno() != MYSQL_OK)<br />
    {<br />
        print("[MySQL] falha ao conectar - veja logs/mysql.log");<br />
        return 1;<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
🇧🇷 O passo a passo por servidor, os caminhos de include e o que fazer quando algo não conecta estão na <a href="https://nullsablex.github.io/mysql_samp/installation/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">página de instalação</span></a>.<br />
🇬🇧 Per-server steps, include paths and troubleshooting are on the <a href="https://nullsablex.github.io/mysql_samp/installation/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">installation page</span></a>.<br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📚 Links</span></span><ul class="mycode_list"><li><a href="https://nullsablex.github.io/mysql_samp/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação</span></a> — 🇧🇷 conexão, queries, cache, ORM, opções e segurança / 🇬🇧 connection, queries, cache, ORM, options, security<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/api-reference/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Referência da API</span></a> — 🇧🇷 as 75 natives, uma a uma / 🇬🇧 all 75 natives<br />
</li>
<li><a href="https://nullsablex.github.io/mysql_samp/migration/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Guia de migração</span></a> — 🇧🇷 para quem vem de outro plugin MySQL / 🇬🇧 coming from another MySQL plugin<br />
</li>
<li><a href="https://github.com/NullSablex/mysql_samp/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">CHANGELOG</span></a> — 🇧🇷 o que mudou em cada versão / 🇬🇧 what changed in each release<br />
</li>
</ul>
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">🇧🇷 Este projeto <span style="font-weight: bold;" class="mycode_b">não</span> é afiliado, endossado nem patrocinado pelo SA-MP ou pelo open.mp. "SA-MP", "open.mp" e "MySQL" pertencem a seus respectivos donos e são citados apenas para descrever compatibilidade.<br />
🇬🇧 This project is <span style="font-weight: bold;" class="mycode_b">not</span> affiliated with, endorsed by, or sponsored by SA-MP or open.mp. "SA-MP", "open.mp", and "MySQL" are trademarks of their respective owners and are mentioned solely to describe compatibility.</span></span><br />
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Dúvidas, sugestões e relatos de bug são bem-vindos — aqui no tópico ou nas issues.<br />
🇬🇧 Questions, suggestions and bug reports are welcome — here or on GitHub issues.</span><br />
<br />
<a href="https://github.com/NullSablex/mysql_samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/mysql_samp</span></a></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Async Crypto - Async Argon2id Password Hashing + Crypto Utilities]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6277</link>
			<pubDate>Fri, 04 Sep 2026 12:37:59 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8418">RoseeasT</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6277</guid>
			<description><![CDATA[<span style="font-weight: bold;" class="mycode_b">Async Crypto</span><br />
<br />
Async Crypto is a modern crypto utility plugin for SA-MP and open.mp, designed to bring non-blocking Argon2id password hashing and practical cryptographic helpers to Pawn gamemodes.<br />
<br />
Unlike traditional password hashing plugins that focus only on bcrypt, Async Crypto provides Argon2id hashing and verification through an asynchronous worker system, so expensive password operations do not block the main server thread.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features</span><br />
<br />
- Async Argon2id password hashing<br />
- Async Argon2id password verification<br />
- Secure random hex generation<br />
- SHA-256 hex hashing<br />
- HMAC-SHA256 signing<br />
- Base64 encode and decode<br />
- SA-MP and open.mp legacy plugin support<br />
- Windows and Linux binaries included<br />
- Simple Pawn include file<br />
- Plug-and-play usage<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Why Argon2id?</span><br />
<br />
Argon2id is a modern password hashing algorithm designed to resist brute-force attacks more effectively than older approaches. It uses configurable memory and time cost parameters, making it a strong choice for account password storage.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Runtime Tested</span><br />
<br />
Tested successfully on an open.mp gamemode.<br />
<br />
Passed tests:<br />
<br />
- Plugin native calls<br />
- Secure random hex<br />
- SHA-256<br />
- HMAC-SHA256<br />
- Base64 encode/decode<br />
- Async Argon2id hash<br />
- Async Argon2id verify<br />
- Register/login flow<br />
- Change-password flow<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Compatibility</span><br />
<br />
Platform | File <br />
Windows | async_crypto.dll<br />
Linux | async_crypto.so<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Repository</span><br />
<br />
GitHub:  <a href="https://github.com/roseeast/async-crypto" target="_blank" rel="noopener" class="mycode_url">https://github.com/roseeast/async-crypto</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Download</span><br />
<br />
<span style="font-style: italic;" class="mycode_i">Download the latest release from the GitHub Releases page.</span>]]></description>
			<content:encoded><![CDATA[<span style="font-weight: bold;" class="mycode_b">Async Crypto</span><br />
<br />
Async Crypto is a modern crypto utility plugin for SA-MP and open.mp, designed to bring non-blocking Argon2id password hashing and practical cryptographic helpers to Pawn gamemodes.<br />
<br />
Unlike traditional password hashing plugins that focus only on bcrypt, Async Crypto provides Argon2id hashing and verification through an asynchronous worker system, so expensive password operations do not block the main server thread.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Features</span><br />
<br />
- Async Argon2id password hashing<br />
- Async Argon2id password verification<br />
- Secure random hex generation<br />
- SHA-256 hex hashing<br />
- HMAC-SHA256 signing<br />
- Base64 encode and decode<br />
- SA-MP and open.mp legacy plugin support<br />
- Windows and Linux binaries included<br />
- Simple Pawn include file<br />
- Plug-and-play usage<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Why Argon2id?</span><br />
<br />
Argon2id is a modern password hashing algorithm designed to resist brute-force attacks more effectively than older approaches. It uses configurable memory and time cost parameters, making it a strong choice for account password storage.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Runtime Tested</span><br />
<br />
Tested successfully on an open.mp gamemode.<br />
<br />
Passed tests:<br />
<br />
- Plugin native calls<br />
- Secure random hex<br />
- SHA-256<br />
- HMAC-SHA256<br />
- Base64 encode/decode<br />
- Async Argon2id hash<br />
- Async Argon2id verify<br />
- Register/login flow<br />
- Change-password flow<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Compatibility</span><br />
<br />
Platform | File <br />
Windows | async_crypto.dll<br />
Linux | async_crypto.so<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Repository</span><br />
<br />
GitHub:  <a href="https://github.com/roseeast/async-crypto" target="_blank" rel="noopener" class="mycode_url">https://github.com/roseeast/async-crypto</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Download</span><br />
<br />
<span style="font-style: italic;" class="mycode_i">Download the latest release from the GitHub Releases page.</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[PawnPro Debugger 0.2.0 — depuração real de Pawn no editor (SA-MP e open.mp)]]></title>
			<link>https://forum.open.mp/showthread.php?tid=6246</link>
			<pubDate>Tue, 01 Sep 2026 06:12:03 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=7983">NullSablex</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=6246</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #E67E22;" class="mycode_color">🐛 PawnPro Debugger v0.2.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Depurador visual (DAP) para Pawn — SA-MP e open.mp</span><br />
<span style="font-style: italic;" class="mycode_i">Visual debugger (DAP) for Pawn — SA-MP and open.mp</span></span><br />
<br />
<a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">github.com/NullSablex/PawnPro-Debugger</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Nova versão do Debugger no ar 🎉 — e <span style="font-weight: bold;" class="mycode_b">hoje sai também a nova versão da extensão PawnPro</span>, com esta atualização do debugger integrada e outras novidades e melhorias no editor. A ideia desta release é simples: parar de depurar gamemode no <span style="font-weight: bold;" class="mycode_b">printf</span>.<br />
<br />
🇬🇧 New Debugger release out 🎉 — and <span style="font-weight: bold;" class="mycode_b">the new PawnPro extension version ships today too</span>, with this debugger update integrated plus other improvements. The point of this release is simple: stop debugging gamemodes with <span style="font-weight: bold;" class="mycode_b">printf</span>.</blockquote>
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">✨ O que tem de novo / What's new</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧭 Call stack completa</span><br />
🇧🇷 A pilha de chamadas inteira, não só a linha atual: função, linha e <span style="font-weight: bold;" class="mycode_b">variáveis de cada frame</span>. Clicou no frame do chamador, a inspeção acompanha.<br />
🇬🇧 The whole call stack, not just the current line: function, line and <span style="font-weight: bold;" class="mycode_b">variables per frame</span>. Click a caller's frame and inspection follows it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">👁️ Data breakpoints</span><br />
🇧🇷 "Break on Value Change": pausa quando um valor muda — globais, locais e <span style="font-weight: bold;" class="mycode_b">elementos de array</span> (<span style="font-weight: bold;" class="mycode_b">arr[3]</span>). Ideal pra achar quem está escrevendo onde não devia.<br />
🇬🇧 "Break on Value Change": pauses when a value changes — globals, locals and <span style="font-weight: bold;" class="mycode_b">array elements</span>. Great for finding who writes where they shouldn't.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">💥 Pausa no erro de runtime, na linha exata</span><br />
🇧🇷 Divisão por zero, índice fora do limite e agora também <span style="font-weight: bold;" class="mycode_b">colisão pilha/heap</span>, <span style="font-weight: bold;" class="mycode_b">underflow de heap</span> e <span style="font-weight: bold;" class="mycode_b">acesso inválido à memória</span>. O debugger pausa <span style="font-weight: bold;" class="mycode_b">antes</span> da VM abortar, mostrando a linha e as variáveis. É conservador por design: se não tiver certeza, não acusa — nada de falso-positivo.<br />
🇬🇧 Divide by zero, out-of-bounds and now <span style="font-weight: bold;" class="mycode_b">stack/heap collision</span>, <span style="font-weight: bold;" class="mycode_b">heap underflow</span> and <span style="font-weight: bold;" class="mycode_b">invalid memory access</span>. It pauses <span style="font-weight: bold;" class="mycode_b">before</span> the VM aborts, showing the line and the variables. Conservative by design: no false positives.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🔢 Arrays e strings de verdade</span><br />
🇧🇷 Arrays expandem elemento a elemento no painel, e array de char aparece como <span style="font-weight: bold;" class="mycode_b">string</span>. Dá pra editar um elemento direto (<span style="font-weight: bold;" class="mycode_b">arr[idx] = 10</span>) no watch ou no console.<br />
🇬🇧 Arrays expand element by element, char arrays show as <span style="font-weight: bold;" class="mycode_b">strings</span>, and you can edit an element right from the watch or console.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧠 Watch, hover e autocomplete</span><br />
🇧🇷 Expressões simples no watch e ao passar o mouse (<span style="font-weight: bold;" class="mycode_b">+ - * / %</span> e comparações, com <span style="font-weight: bold;" class="mycode_b">arr[idx]</span>), e sugestão de variáveis em escopo no console.<br />
🇬🇧 Simple expressions in watch and on hover, plus in-scope variable completion in the console.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧬 Memória crua (hex view)</span><br />
🇧🇷 Qualquer variável abre a memória de dados em hexadecimal, pra quando o valor formatado não conta a história toda.<br />
🇬🇧 Any variable opens raw data memory in hex, for when the formatted value isn't the whole story.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🌍 Mensagens e documentação em mais idiomas</span><br />
🇧🇷 As mensagens seguem o idioma do editor (pt-BR, en, es, ro, ru) e a documentação agora tem versão em inglês.<br />
🇬🇧 Messages follow the editor's language (pt-BR, en, es, ro, ru) and the docs now have an English version.<br />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📋 Changelog completo / Full changelog</span></span><br />
<a href="https://github.com/NullSablex/PawnPro-Debugger/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url">CHANGELOG.md</a><br />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎮 Como se usa na prática / How it works in practice</span></span><br />
<br />
🇧🇷 Você precisa de duas coisas: a extensão <span style="font-weight: bold;" class="mycode_b">PawnPro</span> no editor e o plugin do debugger no seu servidor local — <span style="font-weight: bold;" class="mycode_b">uma vez só</span>. Depois disso:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">F5</span> no gamemode. A extensão recompila com <span style="font-weight: bold;" class="mycode_b">-d3</span>, sobe o servidor e conecta sozinha.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Clique na margem</span> pra colocar um breakpoint. Botão direito nele: <span style="font-weight: bold;" class="mycode_b">condição</span> (<span style="font-weight: bold;" class="mycode_b">playerid == 3</span>), <span style="font-weight: bold;" class="mycode_b">contagem de acertos</span> (<span style="font-weight: bold;" class="mycode_b">%10</span> = a cada 10) ou <span style="font-weight: bold;" class="mycode_b">logpoint</span> — que imprime <span style="font-weight: bold;" class="mycode_b">Vida: {health}</span> no console <span style="font-weight: bold;" class="mycode_b">sem pausar</span> e sem recompilar.<br />
</li>
<li>Pausou: veja variáveis, mude o valor na hora, ande com <span style="font-weight: bold;" class="mycode_b">F10</span>/<span style="font-weight: bold;" class="mycode_b">F11</span> e navegue pelos frames da call stack.<br />
</li>
<li>Botão direito numa variável → <span style="font-weight: bold;" class="mycode_b">Break on Value Change</span> pra parar quando ela mudar.<br />
</li>
</ul>
<br />
🇬🇧 You need two things: the <span style="font-weight: bold;" class="mycode_b">PawnPro</span> extension and the debugger plugin in your local server — <span style="font-weight: bold;" class="mycode_b">once</span>. Then it's F5, breakpoints in the gutter (with conditions, hit counts and logpoints), stepping, and value-change breakpoints from the Variables panel.<br />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🤔 E qual a diferença de não usar? / Why bother?</span></span><br />
<br />
🇧🇷 Sem debugger, o ciclo num servidor SA-MP ou open.mp é conhecido: espalhar <span style="font-weight: bold;" class="mycode_b">printf</span>, recompilar, reiniciar, ler log, apagar os <span style="font-weight: bold;" class="mycode_b">printf</span> — e repetir quando o problema volta. Quando o script dá <span style="font-weight: bold;" class="mycode_b">run time error</span>, você recebe o erro e reconstrói o resto de cabeça.<br />
<br />
Com o debugger, o programa <span style="font-weight: bold;" class="mycode_b">para no ponto</span>: você vê o valor real de cada variável, quem chamou quem, e a <span style="font-weight: bold;" class="mycode_b">linha exata</span> onde o erro ia acontecer — antes de a VM abortar. Sem recompilar pra investigar, sem sujar o código de log temporário.<br />
<br />
🇬🇧 Without a debugger the loop is printf → recompile → restart → read logs → delete the printfs. With it, execution <span style="font-weight: bold;" class="mycode_b">stops at the spot</span>: real values, who called who, and the <span style="font-weight: bold;" class="mycode_b">exact line</span> of a runtime error before the VM aborts.<br />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Importante:</span> isso é uma ferramenta de <span style="font-weight: bold;" class="mycode_b">desenvolvimento local</span>. Enquanto a execução está pausada, o servidor fica congelado — é o comportamento esperado ao depurar passo a passo, e o motivo de não ser algo pra rodar em servidor de produção.<br />
<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Heads-up:</span> this is a <span style="font-weight: bold;" class="mycode_b">local development</span> tool. While paused, the server is frozen — expected when single-stepping, and why it isn't meant for production.</blockquote>
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎬 Em breve / Coming soon</span></span><br />
<br />
🇧🇷 Estão a caminho <span style="font-weight: bold;" class="mycode_b">vídeos curtos</span> mostrando como instalar a extensão, colocar o plugin no servidor e usar o debugger no dia a dia — mini-tutoriais diretos ao ponto. <span style="font-weight: bold;" class="mycode_b">Sem data definida</span> ainda; sai quando estiver bom.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Short videos</span> are on the way: installing the extension, setting up the plugin and using the debugger day to day. <span style="font-weight: bold;" class="mycode_b">No date yet</span> — they ship when they're ready.<br />
<ul class="mycode_list"><li><a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> — 🇧🇷 extensão do editor (nova versão hoje) / 🇬🇧 the editor extension (new version today)<br />
</li>
<li><a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro Debugger</span></a> — 🇧🇷 este projeto / 🇬🇧 this project<br />
</li>
<li><a href="https://pawnpro-debugger.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação / Docs</span></a> — 🇧🇷 começando, recursos e como funciona por dentro<br />
</li>
<li><a href="https://github.com/NullSablex/rust-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">rust-samp</span></a> — 🇧🇷 SDK em Rust que sustenta o debugger / 🇬🇧 the Rust SDK underneath<br />
</li>
</ul>
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Feedback, issues e PRs são bem-vindos — inclusive relato de caso que o debugger não pegou.<br />
🇬🇧 Feedback, issues and PRs are welcome — including cases the debugger failed to catch.</span><br />
<br />
<a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/PawnPro-Debugger</span></a></div>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: 30pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b"><span style="color: #E67E22;" class="mycode_color">🐛 PawnPro Debugger v0.2.0</span></span></span><br />
<br />
<span style="font-size: 15pt;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">Depurador visual (DAP) para Pawn — SA-MP e open.mp</span><br />
<span style="font-style: italic;" class="mycode_i">Visual debugger (DAP) for Pawn — SA-MP and open.mp</span></span><br />
<br />
<a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">github.com/NullSablex/PawnPro-Debugger</span></a></div>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 Nova versão do Debugger no ar 🎉 — e <span style="font-weight: bold;" class="mycode_b">hoje sai também a nova versão da extensão PawnPro</span>, com esta atualização do debugger integrada e outras novidades e melhorias no editor. A ideia desta release é simples: parar de depurar gamemode no <span style="font-weight: bold;" class="mycode_b">printf</span>.<br />
<br />
🇬🇧 New Debugger release out 🎉 — and <span style="font-weight: bold;" class="mycode_b">the new PawnPro extension version ships today too</span>, with this debugger update integrated plus other improvements. The point of this release is simple: stop debugging gamemodes with <span style="font-weight: bold;" class="mycode_b">printf</span>.</blockquote>
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">✨ O que tem de novo / What's new</span></span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧭 Call stack completa</span><br />
🇧🇷 A pilha de chamadas inteira, não só a linha atual: função, linha e <span style="font-weight: bold;" class="mycode_b">variáveis de cada frame</span>. Clicou no frame do chamador, a inspeção acompanha.<br />
🇬🇧 The whole call stack, not just the current line: function, line and <span style="font-weight: bold;" class="mycode_b">variables per frame</span>. Click a caller's frame and inspection follows it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">👁️ Data breakpoints</span><br />
🇧🇷 "Break on Value Change": pausa quando um valor muda — globais, locais e <span style="font-weight: bold;" class="mycode_b">elementos de array</span> (<span style="font-weight: bold;" class="mycode_b">arr[3]</span>). Ideal pra achar quem está escrevendo onde não devia.<br />
🇬🇧 "Break on Value Change": pauses when a value changes — globals, locals and <span style="font-weight: bold;" class="mycode_b">array elements</span>. Great for finding who writes where they shouldn't.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">💥 Pausa no erro de runtime, na linha exata</span><br />
🇧🇷 Divisão por zero, índice fora do limite e agora também <span style="font-weight: bold;" class="mycode_b">colisão pilha/heap</span>, <span style="font-weight: bold;" class="mycode_b">underflow de heap</span> e <span style="font-weight: bold;" class="mycode_b">acesso inválido à memória</span>. O debugger pausa <span style="font-weight: bold;" class="mycode_b">antes</span> da VM abortar, mostrando a linha e as variáveis. É conservador por design: se não tiver certeza, não acusa — nada de falso-positivo.<br />
🇬🇧 Divide by zero, out-of-bounds and now <span style="font-weight: bold;" class="mycode_b">stack/heap collision</span>, <span style="font-weight: bold;" class="mycode_b">heap underflow</span> and <span style="font-weight: bold;" class="mycode_b">invalid memory access</span>. It pauses <span style="font-weight: bold;" class="mycode_b">before</span> the VM aborts, showing the line and the variables. Conservative by design: no false positives.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🔢 Arrays e strings de verdade</span><br />
🇧🇷 Arrays expandem elemento a elemento no painel, e array de char aparece como <span style="font-weight: bold;" class="mycode_b">string</span>. Dá pra editar um elemento direto (<span style="font-weight: bold;" class="mycode_b">arr[idx] = 10</span>) no watch ou no console.<br />
🇬🇧 Arrays expand element by element, char arrays show as <span style="font-weight: bold;" class="mycode_b">strings</span>, and you can edit an element right from the watch or console.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧠 Watch, hover e autocomplete</span><br />
🇧🇷 Expressões simples no watch e ao passar o mouse (<span style="font-weight: bold;" class="mycode_b">+ - * / %</span> e comparações, com <span style="font-weight: bold;" class="mycode_b">arr[idx]</span>), e sugestão de variáveis em escopo no console.<br />
🇬🇧 Simple expressions in watch and on hover, plus in-scope variable completion in the console.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🧬 Memória crua (hex view)</span><br />
🇧🇷 Qualquer variável abre a memória de dados em hexadecimal, pra quando o valor formatado não conta a história toda.<br />
🇬🇧 Any variable opens raw data memory in hex, for when the formatted value isn't the whole story.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">🌍 Mensagens e documentação em mais idiomas</span><br />
🇧🇷 As mensagens seguem o idioma do editor (pt-BR, en, es, ro, ru) e a documentação agora tem versão em inglês.<br />
🇬🇧 Messages follow the editor's language (pt-BR, en, es, ro, ru) and the docs now have an English version.<br />
<br />
<span style="font-size: 18pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">📋 Changelog completo / Full changelog</span></span><br />
<a href="https://github.com/NullSablex/PawnPro-Debugger/blob/master/CHANGELOG.md" target="_blank" rel="noopener" class="mycode_url">CHANGELOG.md</a><br />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎮 Como se usa na prática / How it works in practice</span></span><br />
<br />
🇧🇷 Você precisa de duas coisas: a extensão <span style="font-weight: bold;" class="mycode_b">PawnPro</span> no editor e o plugin do debugger no seu servidor local — <span style="font-weight: bold;" class="mycode_b">uma vez só</span>. Depois disso:<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">F5</span> no gamemode. A extensão recompila com <span style="font-weight: bold;" class="mycode_b">-d3</span>, sobe o servidor e conecta sozinha.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Clique na margem</span> pra colocar um breakpoint. Botão direito nele: <span style="font-weight: bold;" class="mycode_b">condição</span> (<span style="font-weight: bold;" class="mycode_b">playerid == 3</span>), <span style="font-weight: bold;" class="mycode_b">contagem de acertos</span> (<span style="font-weight: bold;" class="mycode_b">%10</span> = a cada 10) ou <span style="font-weight: bold;" class="mycode_b">logpoint</span> — que imprime <span style="font-weight: bold;" class="mycode_b">Vida: {health}</span> no console <span style="font-weight: bold;" class="mycode_b">sem pausar</span> e sem recompilar.<br />
</li>
<li>Pausou: veja variáveis, mude o valor na hora, ande com <span style="font-weight: bold;" class="mycode_b">F10</span>/<span style="font-weight: bold;" class="mycode_b">F11</span> e navegue pelos frames da call stack.<br />
</li>
<li>Botão direito numa variável → <span style="font-weight: bold;" class="mycode_b">Break on Value Change</span> pra parar quando ela mudar.<br />
</li>
</ul>
<br />
🇬🇧 You need two things: the <span style="font-weight: bold;" class="mycode_b">PawnPro</span> extension and the debugger plugin in your local server — <span style="font-weight: bold;" class="mycode_b">once</span>. Then it's F5, breakpoints in the gutter (with conditions, hit counts and logpoints), stepping, and value-change breakpoints from the Variables panel.<br />
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🤔 E qual a diferença de não usar? / Why bother?</span></span><br />
<br />
🇧🇷 Sem debugger, o ciclo num servidor SA-MP ou open.mp é conhecido: espalhar <span style="font-weight: bold;" class="mycode_b">printf</span>, recompilar, reiniciar, ler log, apagar os <span style="font-weight: bold;" class="mycode_b">printf</span> — e repetir quando o problema volta. Quando o script dá <span style="font-weight: bold;" class="mycode_b">run time error</span>, você recebe o erro e reconstrói o resto de cabeça.<br />
<br />
Com o debugger, o programa <span style="font-weight: bold;" class="mycode_b">para no ponto</span>: você vê o valor real de cada variável, quem chamou quem, e a <span style="font-weight: bold;" class="mycode_b">linha exata</span> onde o erro ia acontecer — antes de a VM abortar. Sem recompilar pra investigar, sem sujar o código de log temporário.<br />
<br />
🇬🇧 Without a debugger the loop is printf → recompile → restart → read logs → delete the printfs. With it, execution <span style="font-weight: bold;" class="mycode_b">stops at the spot</span>: real values, who called who, and the <span style="font-weight: bold;" class="mycode_b">exact line</span> of a runtime error before the VM aborts.<br />
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>🇧🇷 <span style="font-weight: bold;" class="mycode_b">Importante:</span> isso é uma ferramenta de <span style="font-weight: bold;" class="mycode_b">desenvolvimento local</span>. Enquanto a execução está pausada, o servidor fica congelado — é o comportamento esperado ao depurar passo a passo, e o motivo de não ser algo pra rodar em servidor de produção.<br />
<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Heads-up:</span> this is a <span style="font-weight: bold;" class="mycode_b">local development</span> tool. While paused, the server is frozen — expected when single-stepping, and why it isn't meant for production.</blockquote>
<br />
<span style="font-size: 22pt;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">🎬 Em breve / Coming soon</span></span><br />
<br />
🇧🇷 Estão a caminho <span style="font-weight: bold;" class="mycode_b">vídeos curtos</span> mostrando como instalar a extensão, colocar o plugin no servidor e usar o debugger no dia a dia — mini-tutoriais diretos ao ponto. <span style="font-weight: bold;" class="mycode_b">Sem data definida</span> ainda; sai quando estiver bom.<br />
🇬🇧 <span style="font-weight: bold;" class="mycode_b">Short videos</span> are on the way: installing the extension, setting up the plugin and using the debugger day to day. <span style="font-weight: bold;" class="mycode_b">No date yet</span> — they ship when they're ready.<br />
<ul class="mycode_list"><li><a href="https://github.com/NullSablex/PawnPro" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro</span></a> — 🇧🇷 extensão do editor (nova versão hoje) / 🇬🇧 the editor extension (new version today)<br />
</li>
<li><a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">PawnPro Debugger</span></a> — 🇧🇷 este projeto / 🇬🇧 this project<br />
</li>
<li><a href="https://pawnpro-debugger.nullsablex.com/" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">Documentação / Docs</span></a> — 🇧🇷 começando, recursos e como funciona por dentro<br />
</li>
<li><a href="https://github.com/NullSablex/rust-samp" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">rust-samp</span></a> — 🇧🇷 SDK em Rust que sustenta o debugger / 🇬🇧 the Rust SDK underneath<br />
</li>
</ul>
<br />
<div style="text-align: center;" class="mycode_align"><span style="font-style: italic;" class="mycode_i">🇧🇷 Feedback, issues e PRs são bem-vindos — inclusive relato de caso que o debugger não pegou.<br />
🇬🇧 Feedback, issues and PRs are welcome — including cases the debugger failed to catch.</span><br />
<br />
<a href="https://github.com/NullSablex/PawnPro-Debugger" target="_blank" rel="noopener" class="mycode_url"><span style="font-weight: bold;" class="mycode_b">⭐ github.com/NullSablex/PawnPro-Debugger</span></a></div>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[SmartEvents - a plugin for timed events]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4575</link>
			<pubDate>Sun, 09 Aug 2026 08:27:02 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=5481">email.d.value</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4575</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-weight: bold;" class="mycode_b"><span style="font-size: large;" class="mycode_size">SmartEvents</span></span><br />
SA-MP/OMP plugin for managing timed player events: Mute, Jail, VIP, etc.</div>
<br />
<span style="font-weight: bold;" class="mycode_b">How it works</span><br />
When creating an event you need to specify:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Event name</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Callback</span> - called once when the time expires. If the remaining time is more than 24 hours, the callback isn't added to the queue until the next server restart, which optimizes performance.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Time type</span> - there are two types:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">false - offline:</span> time counts down even when the player isn't on the server. Saved once when the event is assigned.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">true - online:</span> time counts down only while the player is on the server. Saved when assigned and every time the player disconnects.<br />
</li>
</ul>
</li>
</ul>
<span style="font-weight: bold;" class="mycode_b">Crash protection</span><br />
Even for online events, data loss on crash isn't a problem.<br />
When an event is assigned, the necessary data is written to a temporary table, and it's removed when the player disconnects.<br />
If the server crashes, the data stays there.<br />
On restart the plugin checks the temporary table, calculates the difference between the saved time and the current startup time, and updates the main table.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Requirements</span><ul class="mycode_list"><li>Automatic server restart once a day (present on all decent servers)<br />
</li>
<li>An auto-restart-on-crash script (present by default on all hosting providers; set up manually on a VPS)<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Data storage</span><br />
All data is stored in an SQLite database at <span style="font-weight: bold;" class="mycode_b">scriptfiles/SmartEvents.db</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Integration</span><br />
Call two functions in your gamemode - one after login, another after registration:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>public OnPlayerLogin(playerid)<br />
{<br />
    // Your account loading<br />
    cache_get_value_name_int(0, "ID", PlayerInfo[playerid][pID]);<br />
<br />
    SE_OnPlayerLogin(playerid, PlayerInfo[playerid][pID]);<br />
}<br />
<br />
public OnPlayerSignIn(playerid)<br />
{<br />
    // When the player has registered<br />
    PlayerInfo[playerid][pID] = cache_insert_id();<br />
    SE_OnPlayerSignIn(playerid, PlayerInfo[playerid][pID]);<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Usage example</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new SE:gPlayerMute;<br />
new SE:gPlayerJail;<br />
<br />
public OnGameModeInit()<br />
{<br />
    SE_SetLanguage("en");<br />
    gPlayerMute = SE_AddEvent("mute", "OnMuteExpired", true); // online - in-game time only<br />
    gPlayerJail = SE_AddEvent("jail", "OnJailExpired", true); // online - in-game time only<br />
}<br />
<br />
CMD:mute(playerid, params[])<br />
{<br />
    SE_SetPlayerEvent(targetid, gPlayerMute, SE_MinutesToSeconds(30));<br />
    SendClientMessage(targetid, -1, "You have been muted for 30 minutes");<br />
}<br />
<br />
CMD:unmute(playerid, params[])<br />
{<br />
    SE_RemovePlayerEvent(targetid, gPlayerMute);<br />
    SendClientMessage(targetid, -1, "Your mute has been removed");<br />
}<br />
<br />
CMD:jail(playerid, params[])<br />
{<br />
    SE_SetPlayerEvent(targetid, gPlayerJail, SE_HoursToSeconds(1));<br />
    SendClientMessage(targetid, -1, "You have been jailed for 1 hour");<br />
}<br />
<br />
public OnPlayerText(playerid, text[])<br />
{<br />
    if (SE_IsPlayerEventActive(playerid, gPlayerMute))<br />
    {<br />
        SendClientMessage(playerid, -1, "You are muted");<br />
        return 0;<br />
    }<br />
    return 1;<br />
}<br />
<br />
public OnPlayerSpawn(playerid)<br />
{<br />
    if (SE_IsPlayerEventActive(playerid, gPlayerJail))<br />
    {<br />
        SetPlayerPos(playerid, x, y, z);<br />
    }<br />
    return 1;<br />
}<br />
<br />
SE_Event:OnMuteExpired(playerid)<br />
{<br />
    SendClientMessage(playerid, -1, "Your mute has expired");<br />
}<br />
<br />
SE_Event:OnJailExpired(playerid)<br />
{<br />
    SetPlayerPos(playerid, x, y, z);<br />
    SendClientMessage(playerid, -1, "Your jail time has expired");<br />
}</code></div></div><br />
Why is this better than subtracting and saving every second?<br />
<br />
DB queries: Tick &amp; Save - every second per player / SmartEvents - on assignment + on disconnect<br />
Callback calls: Tick &amp; Save - every second / SmartEvents - once, on expiration<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Benchmark</span><br />
100 players with a 5-hour mute and 15 reconnects per player (reconnect for the plugin only).<br />
<img src="https://raw.githubusercontent.com/i-Saibot/SmartEvents/main/Benchmark.png" loading="lazy"  width="550" height="350" alt="[Image: Benchmark.png]" class="mycode_img" /><br />
<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Documentation &gt;&gt;</span> <a href="https://github.com/i-Saibot/SmartEvents/wiki" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/SmartEvents/wiki</a><br />
<span style="font-weight: bold;" class="mycode_b">Release &gt;&gt;</span> <a href="https://github.com/i-Saibot/SmartEvents/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/SmartEvents/releases</a>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-weight: bold;" class="mycode_b"><span style="font-size: large;" class="mycode_size">SmartEvents</span></span><br />
SA-MP/OMP plugin for managing timed player events: Mute, Jail, VIP, etc.</div>
<br />
<span style="font-weight: bold;" class="mycode_b">How it works</span><br />
When creating an event you need to specify:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Event name</span><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Callback</span> - called once when the time expires. If the remaining time is more than 24 hours, the callback isn't added to the queue until the next server restart, which optimizes performance.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Time type</span> - there are two types:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">false - offline:</span> time counts down even when the player isn't on the server. Saved once when the event is assigned.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">true - online:</span> time counts down only while the player is on the server. Saved when assigned and every time the player disconnects.<br />
</li>
</ul>
</li>
</ul>
<span style="font-weight: bold;" class="mycode_b">Crash protection</span><br />
Even for online events, data loss on crash isn't a problem.<br />
When an event is assigned, the necessary data is written to a temporary table, and it's removed when the player disconnects.<br />
If the server crashes, the data stays there.<br />
On restart the plugin checks the temporary table, calculates the difference between the saved time and the current startup time, and updates the main table.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Requirements</span><ul class="mycode_list"><li>Automatic server restart once a day (present on all decent servers)<br />
</li>
<li>An auto-restart-on-crash script (present by default on all hosting providers; set up manually on a VPS)<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">Data storage</span><br />
All data is stored in an SQLite database at <span style="font-weight: bold;" class="mycode_b">scriptfiles/SmartEvents.db</span>.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Integration</span><br />
Call two functions in your gamemode - one after login, another after registration:<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>public OnPlayerLogin(playerid)<br />
{<br />
    // Your account loading<br />
    cache_get_value_name_int(0, "ID", PlayerInfo[playerid][pID]);<br />
<br />
    SE_OnPlayerLogin(playerid, PlayerInfo[playerid][pID]);<br />
}<br />
<br />
public OnPlayerSignIn(playerid)<br />
{<br />
    // When the player has registered<br />
    PlayerInfo[playerid][pID] = cache_insert_id();<br />
    SE_OnPlayerSignIn(playerid, PlayerInfo[playerid][pID]);<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Usage example</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>new SE:gPlayerMute;<br />
new SE:gPlayerJail;<br />
<br />
public OnGameModeInit()<br />
{<br />
    SE_SetLanguage("en");<br />
    gPlayerMute = SE_AddEvent("mute", "OnMuteExpired", true); // online - in-game time only<br />
    gPlayerJail = SE_AddEvent("jail", "OnJailExpired", true); // online - in-game time only<br />
}<br />
<br />
CMD:mute(playerid, params[])<br />
{<br />
    SE_SetPlayerEvent(targetid, gPlayerMute, SE_MinutesToSeconds(30));<br />
    SendClientMessage(targetid, -1, "You have been muted for 30 minutes");<br />
}<br />
<br />
CMD:unmute(playerid, params[])<br />
{<br />
    SE_RemovePlayerEvent(targetid, gPlayerMute);<br />
    SendClientMessage(targetid, -1, "Your mute has been removed");<br />
}<br />
<br />
CMD:jail(playerid, params[])<br />
{<br />
    SE_SetPlayerEvent(targetid, gPlayerJail, SE_HoursToSeconds(1));<br />
    SendClientMessage(targetid, -1, "You have been jailed for 1 hour");<br />
}<br />
<br />
public OnPlayerText(playerid, text[])<br />
{<br />
    if (SE_IsPlayerEventActive(playerid, gPlayerMute))<br />
    {<br />
        SendClientMessage(playerid, -1, "You are muted");<br />
        return 0;<br />
    }<br />
    return 1;<br />
}<br />
<br />
public OnPlayerSpawn(playerid)<br />
{<br />
    if (SE_IsPlayerEventActive(playerid, gPlayerJail))<br />
    {<br />
        SetPlayerPos(playerid, x, y, z);<br />
    }<br />
    return 1;<br />
}<br />
<br />
SE_Event:OnMuteExpired(playerid)<br />
{<br />
    SendClientMessage(playerid, -1, "Your mute has expired");<br />
}<br />
<br />
SE_Event:OnJailExpired(playerid)<br />
{<br />
    SetPlayerPos(playerid, x, y, z);<br />
    SendClientMessage(playerid, -1, "Your jail time has expired");<br />
}</code></div></div><br />
Why is this better than subtracting and saving every second?<br />
<br />
DB queries: Tick &amp; Save - every second per player / SmartEvents - on assignment + on disconnect<br />
Callback calls: Tick &amp; Save - every second / SmartEvents - once, on expiration<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Benchmark</span><br />
100 players with a 5-hour mute and 15 reconnects per player (reconnect for the plugin only).<br />
<img src="https://raw.githubusercontent.com/i-Saibot/SmartEvents/main/Benchmark.png" loading="lazy"  width="550" height="350" alt="[Image: Benchmark.png]" class="mycode_img" /><br />
<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Documentation &gt;&gt;</span> <a href="https://github.com/i-Saibot/SmartEvents/wiki" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/SmartEvents/wiki</a><br />
<span style="font-weight: bold;" class="mycode_b">Release &gt;&gt;</span> <a href="https://github.com/i-Saibot/SmartEvents/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/SmartEvents/releases</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[PawnMap - a plugin for creating maps in Pawn]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4574</link>
			<pubDate>Sun, 09 Aug 2026 08:19:28 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=5481">email.d.value</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4574</guid>
			<description><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">PawnMap</span> - a plugin for creating maps in Pawn</span><br />
</div>
<br />
<span style="font-weight: bold;" class="mycode_b">Main goal:</span> to make a map for Pawn that is as clear and performant as possible.<br />
So it could be used in regular systems, without significantly falling behind raw Pawn arrays, and where some data operation is needed (search, sorting), it would even be faster.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">All detailed documentation with examples is in my repository</span> &gt;&gt; <a href="https://github.com/i-Saibot/PawnMap/wiki" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/PawnMap/wiki</a><br />
Here I'll just briefly show the list of functions and a couple of examples:<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Functions</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Map_Create - Creates a new map instance.<br />
Map_Destroy - Deletes a map instance.<br />
Map_IsValid - Checks whether the map exists in memory.<br />
Map_Clear - Completely clears the map.<br />
Map_Clone - Creates a full copy of an existing map.<br />
Map_Merge - Merges two maps.<br />
Map_Set - Sets a set of values for the specified key.<br />
Map_Get - Retrieves all data stored under the specified key.<br />
Map_RemoveKey - Removes the specified key.<br />
Map_SafeRemoveKey - Safely removes the specified key inside a loop.<br />
Map_RenameKey - Changes the identifier of an existing key to a new one.<br />
Map_Swap - Swaps the data of two specified keys.<br />
Map_ContainsKey - Checks whether the specified key exists in the map.<br />
Map_GetKeyByIndex - Gets the key value by its index.<br />
Map_GetKeyCount - Gets the number of keys in the map.<br />
Map_FindKeyByField - Searches for a key by its value.<br />
Map_SortByKey - Sorts all keys in the map according to the selected order.<br />
Map_SortByField - Sorts entries in the map based on the values of an enum field.<br />
Map_SetString - Helper function for safely writing a string into an array.<br />
mapfor - A loop for iterating over keys in a map.<br />
Map_StringKeyToIntor - Converts a string key to an int for the map.<br />
Map_GetIdByStringKey - Finds and returns the integer (ID) of an existing string key in the map.<br />
Map_ContainsStringKey - Checks whether an entry with the specified string key exists in the map.<br />
Map_GetStringById - Gets the string name of a key by its numeric identifier (ID).</code></div></div><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Dm Zone</span><br />
Let's say we need to make a DM arena where we'll track the number of kills, deaths, and damage.<br />
At the end of the round we need to sort the list, print the data, and clear the map for the next round.<br />
In this map we use playerid as the key.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>enum e_dm_zone<br />
{<br />
    Kills,<br />
    Death,<br />
    Float:Damage<br />
}<br />
new PawnMap:MapDmZone;<br />
<br />
public OnGameModeInit()<br />
{<br />
    // Create the map on mode start<br />
    MapDmZone = Map_Create();<br />
    return 1;<br />
}<br />
<br />
public OnGameModeExit()<br />
{<br />
    // Destroy the map<br />
    Map_Destroy(MapDmZone);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerTakeDamage(playerid, issuerid, Float:amount, weaponid, bodypart)<br />
{<br />
    // Check whether the player is in the DM zone<br />
<br />
    static data[e_dm_zone];<br />
<br />
    // Get the player's current data to update it<br />
    Map_Get(MapDmZone, playerid, data);<br />
<br />
    data[Damage] += amount;<br />
<br />
    // Update only the damage<br />
    Map_Set(MapDmZone, playerid, data);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerDeath(playerid, killerid, reason)<br />
{<br />
    // Check whether the player is in the DM zone<br />
<br />
    static data[e_dm_zone];<br />
<br />
    // Update deaths (Death)<br />
    Map_Get(MapDmZone, playerid, data);<br />
    data[Death] += 1;<br />
    Map_Set(MapDmZone, playerid, data);<br />
<br />
    // Update kills (Kill)<br />
    if(killerid != INVALID_PLAYER_ID)<br />
    {<br />
        Map_Get(MapDmZone, killerid, data);<br />
        data[Kills] += 1;<br />
        Map_Set(MapDmZone, killerid, data);<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock DmZoneRoundFinish()<br />
{<br />
    // Sort in descending order (players with more kills first)<br />
    Map_SortByField(MapDmZone, e_dm_zone:Kills, MAP_SORT_DESC);<br />
<br />
    static data[e_dm_zone];<br />
    new string[144];<br />
<br />
    mapfor(MapDmZone, i)<br />
    {<br />
        Map_Get(MapDmZone, i, data);<br />
<br />
        format(string, sizeof(string),<br />
            "[DM ZONE] playerid %d | kills %d | death %d | damage %.2f",<br />
            i,<br />
            data[Kills],<br />
            data[Death],<br />
            data[Damage]<br />
        );<br />
        SendClientMessageToAll(-1, string);<br />
    }<br />
    <br />
    // Clear the data for the next round<br />
    Map_Clear(MapDmZone);<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Inventory</span><br />
Let's say we need to implement a simple inventory with a basic set of functions.<br />
In this architecture we use the slot ID as the unique key (Key), and the item structure as the value (Value).<br />
This allows efficient management of slots, moving items, and quickly checking whether the bag is full.<br />
In this system we use the slot ID as the key (Key), and the item structure as the value (Value).<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>const INVENTORY_MAX_SLOTS = 15;<br />
<br />
enum e_inventory<br />
{<br />
    Item,<br />
    Amount,<br />
    Name[24]<br />
}<br />
new PawnMap:MapInventory[MAX_PLAYERS] = {INVALID_MAP_ID, ...};<br />
<br />
public OnPlayerConnect(playerid)<br />
{<br />
    // Create a map for each player on connect<br />
    MapInventory[playerid] = Map_Create();<br />
    return 1;<br />
}<br />
<br />
public OnPlayerDisconnect(playerid, reason)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
<br />
    // Check validity before deleting<br />
    if (Map_IsValid(mapid))<br />
    {<br />
        // Fully destroy the map and free the memory in the plugin<br />
        Map_Destroy(mapid);<br />
        <br />
        // Reset the variable to its initial state<br />
        MapInventory[playerid] = INVALID_MAP_ID;<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock GiveInventoryItems(playerid, itemid, amount, const name[])<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    // Check whether this item already exists in the inventory (by the Item field)<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    static data[e_inventory];<br />
<br />
    if (slotid != INVALID_MAP_KEY_ID)<br />
    {<br />
        // If the item is found - increase the amount<br />
        Map_Get(mapid, slotid, data);<br />
        data[Amount] += amount;<br />
        Map_Set(mapid, slotid, data);<br />
    }<br />
    else<br />
    {<br />
        // If the item is new - check the slot limit<br />
        if (Map_GetKeyCount(mapid) &gt;= INVENTORY_MAX_SLOTS)<br />
        {<br />
            SendClientMessage(playerid, -1, "No free slot for the item.");<br />
            return 0;<br />
        }<br />
<br />
        new free_slotid = Map_GetFreeKey(mapid);<br />
        if (free_slotid == INVALID_MAP_KEY_ID)<br />
        {<br />
            SendClientMessage(playerid, -1, "Error finding a free slot.");<br />
            return 0;<br />
        }<br />
<br />
        data[Item] = itemid;<br />
        data[Amount] = amount;<br />
        Map_SetString(data[Name], name);<br />
<br />
        Map_Set(mapid, free_slotid, data);<br />
    }<br />
    SendClientMessage(playerid, -1, "Inventory updated.");<br />
    return 1;<br />
}<br />
<br />
stock RemoveInventoryItems(playerid, itemid)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    if (slotid == INVALID_MAP_KEY_ID)<br />
    {<br />
        SendClientMessage(playerid, -1, "Error: item not found.");<br />
        return 0;<br />
    }<br />
    Map_RemoveKey(mapid, slotid);<br />
    SendClientMessage(playerid, -1, "Item removed.");<br />
    return 1;<br />
}<br />
<br />
stock UseInventoryItems(playerid, itemid, amount)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    if (slotid == INVALID_MAP_KEY_ID)<br />
    {<br />
        SendClientMessage(playerid, -1, "Error: item not found.");<br />
        return 0;<br />
    }<br />
<br />
    static data[e_inventory];<br />
    Map_Get(mapid, slotid, data);<br />
<br />
    data[Amount] -= amount;<br />
<br />
    if (data[Amount] &lt;= 0)<br />
    {<br />
        Map_RemoveKey(mapid, slotid);<br />
    }<br />
    else<br />
    {<br />
        Map_Set(mapid, slotid, data);<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock SwapInventoryItems(playerid, slot_1, slot_2)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
<br />
    if (Map_Swap(mapid, slot_1, slot_2))<br />
    {<br />
        SendClientMessage(playerid, -1, "Items successfully moved.");<br />
    }<br />
    else<br />
    {<br />
        SendClientMessage(playerid, -1, "Move error.");<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock ShowInventory(playerid)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new string[1024];<br />
    <br />
    mapfor(mapid, slotid)<br />
    {<br />
        static data[e_inventory];<br />
        Map_Get(mapid, slotid, data);<br />
<br />
        format(string, sizeof(string),<br />
            "%s№%d&#92;t%s&#92;tAmount: %d&#92;n",<br />
            string,<br />
            slotid,<br />
            data[Name],<br />
            data[Amount]<br />
        );<br />
    }<br />
    ShowPlayerDialog(playerid, 1000, DIALOG_STYLE_LIST, "Inventory", string, "Select", "Close");<br />
    return 1;<br />
}<br />
<br />
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])<br />
{<br />
    if (dialogid == 1000)<br />
    {<br />
        if (!response) return 0;<br />
<br />
        new slotid = listitem;<br />
        new PawnMap:mapid = MapInventory[playerid];<br />
<br />
        static data[e_inventory];<br />
        if (Map_Get(mapid, slotid, data))<br />
        {<br />
            new string[144];<br />
            format(string, sizeof(string),<br />
                "You selected - Name: %s | ID: %d | Amount: %d",<br />
                data[Name], data[Item], data[Amount]<br />
            );<br />
            SendClientMessage(playerid, -1, string);<br />
        }<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Speed test </span><br />
<br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr>
<th class="tcat" align="middle"><strong>OPERATION</strong></th>
<th class="tcat" align="middle"><strong>PawnMap</strong></th>
<th class="tcat" align="middle"><strong>Raw Array</strong></th>
<th class="tcat" align="middle"><strong>DIFF (RA/PM)</strong></th>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CREATE &amp; DEL</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">x0.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SET</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">x0.5</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">GET</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">x2.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">ADD</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">x0.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">FIND</td>
<td class="trow1" valign="top" align="center">8 ms</td>
<td class="trow1" valign="top" align="center">417 ms</td>
<td class="trow1" valign="top" align="center">x52.1</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CONTAINS</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">1052 ms</td>
<td class="trow1" valign="top" align="center">x1052.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">REMOVE</td>
<td class="trow1" valign="top" align="center">44 ms</td>
<td class="trow1" valign="top" align="center">1901 ms</td>
<td class="trow1" valign="top" align="center">x43.2</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SWAP</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">4 ms</td>
<td class="trow1" valign="top" align="center">x4.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SORT</td>
<td class="trow1" valign="top" align="center">4 ms</td>
<td class="trow1" valign="top" align="center">18 ms</td>
<td class="trow1" valign="top" align="center">x4.5</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SET STR</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">x1.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CONS STR</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">1394 ms</td>
<td class="trow1" valign="top" align="center">x1394.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">TOTAL TIME</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">63 ms</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">4790 ms</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">x76.0</span></td>
</tr>
</table>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>At this point, only an integer (int) can be a key. In four years of working on projects, I only needed a string identifier for a system once;I don't recall other cases where it would have been necessary. To avoid cluttering the API with duplicate functions with the _Str postfix (which would need to be added to practically every function) and to avoid reducing map performance, I decided not to implement string key support. Instead, I implemented the ability to convert a string into an integer key.</blockquote>
<br />
<span style="font-weight: bold;" class="mycode_b">Example</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>enum inventory_struct<br />
{<br />
    Itemid,<br />
    Amount<br />
}<br />
<br />
public OnGameModeInit()<br />
{<br />
    new PawnMap:mapid = Map_Create();<br />
<br />
    // 1. Convert the string "Deagle" into a numeric ID and write the data<br />
    new keyid = Map_StringKeyToInt(mapid, "Deagle");<br />
<br />
    if (keyid != INVALID_MAP_KEY_ID)<br />
    {<br />
        new data[inventory_struct];<br />
<br />
        data[Itemid] = 24;<br />
        data[Amount] = 100;<br />
<br />
        // Save the data array under this ID<br />
        Map_Set(mapid, keyid, data);<br />
<br />
        printf("String 'Deagle' successfully converted to ID: %d", keyid);<br />
    }<br />
<br />
    // 2. Get the ID by string (using the already created variable, without 'new')<br />
    keyid = Map_GetIdByStringKey(mapid, "Deagle");<br />
<br />
    if (keyid != INVALID_MAP_KEY_ID)<br />
    {<br />
        printf("ID for key 'Deagle' found: %d", keyid);<br />
    }<br />
    else<br />
    {<br />
        printf("This string key does not exist in the map.");<br />
    }<br />
<br />
    // 3. Check for the key directly<br />
    if (Map_ContainsStringKey(mapid, "Deagle"))<br />
    {<br />
        printf("Key 'Deagle' exists in this map.");<br />
    }<br />
    else<br />
    {<br />
        printf("Key 'Deagle' not found.");<br />
    }<br />
<br />
    // 4. Convert the numeric ID back to a string<br />
    new buffer[32];<br />
<br />
    if (Map_GetStringById(mapid, keyid, buffer))<br />
    {<br />
        printf("Name of key under ID %d - '%s'", keyid, buffer);<br />
    }<br />
<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Download:</span> &gt;&gt; <a href="https://github.com/i-Saibot/PawnMap/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/PawnMap/releases</a></span>]]></description>
			<content:encoded><![CDATA[<div style="text-align: center;" class="mycode_align"><span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">PawnMap</span> - a plugin for creating maps in Pawn</span><br />
</div>
<br />
<span style="font-weight: bold;" class="mycode_b">Main goal:</span> to make a map for Pawn that is as clear and performant as possible.<br />
So it could be used in regular systems, without significantly falling behind raw Pawn arrays, and where some data operation is needed (search, sorting), it would even be faster.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">All detailed documentation with examples is in my repository</span> &gt;&gt; <a href="https://github.com/i-Saibot/PawnMap/wiki" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/PawnMap/wiki</a><br />
Here I'll just briefly show the list of functions and a couple of examples:<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Functions</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>Map_Create - Creates a new map instance.<br />
Map_Destroy - Deletes a map instance.<br />
Map_IsValid - Checks whether the map exists in memory.<br />
Map_Clear - Completely clears the map.<br />
Map_Clone - Creates a full copy of an existing map.<br />
Map_Merge - Merges two maps.<br />
Map_Set - Sets a set of values for the specified key.<br />
Map_Get - Retrieves all data stored under the specified key.<br />
Map_RemoveKey - Removes the specified key.<br />
Map_SafeRemoveKey - Safely removes the specified key inside a loop.<br />
Map_RenameKey - Changes the identifier of an existing key to a new one.<br />
Map_Swap - Swaps the data of two specified keys.<br />
Map_ContainsKey - Checks whether the specified key exists in the map.<br />
Map_GetKeyByIndex - Gets the key value by its index.<br />
Map_GetKeyCount - Gets the number of keys in the map.<br />
Map_FindKeyByField - Searches for a key by its value.<br />
Map_SortByKey - Sorts all keys in the map according to the selected order.<br />
Map_SortByField - Sorts entries in the map based on the values of an enum field.<br />
Map_SetString - Helper function for safely writing a string into an array.<br />
mapfor - A loop for iterating over keys in a map.<br />
Map_StringKeyToIntor - Converts a string key to an int for the map.<br />
Map_GetIdByStringKey - Finds and returns the integer (ID) of an existing string key in the map.<br />
Map_ContainsStringKey - Checks whether an entry with the specified string key exists in the map.<br />
Map_GetStringById - Gets the string name of a key by its numeric identifier (ID).</code></div></div><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Dm Zone</span><br />
Let's say we need to make a DM arena where we'll track the number of kills, deaths, and damage.<br />
At the end of the round we need to sort the list, print the data, and clear the map for the next round.<br />
In this map we use playerid as the key.<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>enum e_dm_zone<br />
{<br />
    Kills,<br />
    Death,<br />
    Float:Damage<br />
}<br />
new PawnMap:MapDmZone;<br />
<br />
public OnGameModeInit()<br />
{<br />
    // Create the map on mode start<br />
    MapDmZone = Map_Create();<br />
    return 1;<br />
}<br />
<br />
public OnGameModeExit()<br />
{<br />
    // Destroy the map<br />
    Map_Destroy(MapDmZone);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerTakeDamage(playerid, issuerid, Float:amount, weaponid, bodypart)<br />
{<br />
    // Check whether the player is in the DM zone<br />
<br />
    static data[e_dm_zone];<br />
<br />
    // Get the player's current data to update it<br />
    Map_Get(MapDmZone, playerid, data);<br />
<br />
    data[Damage] += amount;<br />
<br />
    // Update only the damage<br />
    Map_Set(MapDmZone, playerid, data);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerDeath(playerid, killerid, reason)<br />
{<br />
    // Check whether the player is in the DM zone<br />
<br />
    static data[e_dm_zone];<br />
<br />
    // Update deaths (Death)<br />
    Map_Get(MapDmZone, playerid, data);<br />
    data[Death] += 1;<br />
    Map_Set(MapDmZone, playerid, data);<br />
<br />
    // Update kills (Kill)<br />
    if(killerid != INVALID_PLAYER_ID)<br />
    {<br />
        Map_Get(MapDmZone, killerid, data);<br />
        data[Kills] += 1;<br />
        Map_Set(MapDmZone, killerid, data);<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock DmZoneRoundFinish()<br />
{<br />
    // Sort in descending order (players with more kills first)<br />
    Map_SortByField(MapDmZone, e_dm_zone:Kills, MAP_SORT_DESC);<br />
<br />
    static data[e_dm_zone];<br />
    new string[144];<br />
<br />
    mapfor(MapDmZone, i)<br />
    {<br />
        Map_Get(MapDmZone, i, data);<br />
<br />
        format(string, sizeof(string),<br />
            "[DM ZONE] playerid %d | kills %d | death %d | damage %.2f",<br />
            i,<br />
            data[Kills],<br />
            data[Death],<br />
            data[Damage]<br />
        );<br />
        SendClientMessageToAll(-1, string);<br />
    }<br />
    <br />
    // Clear the data for the next round<br />
    Map_Clear(MapDmZone);<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Inventory</span><br />
Let's say we need to implement a simple inventory with a basic set of functions.<br />
In this architecture we use the slot ID as the unique key (Key), and the item structure as the value (Value).<br />
This allows efficient management of slots, moving items, and quickly checking whether the bag is full.<br />
In this system we use the slot ID as the key (Key), and the item structure as the value (Value).<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>const INVENTORY_MAX_SLOTS = 15;<br />
<br />
enum e_inventory<br />
{<br />
    Item,<br />
    Amount,<br />
    Name[24]<br />
}<br />
new PawnMap:MapInventory[MAX_PLAYERS] = {INVALID_MAP_ID, ...};<br />
<br />
public OnPlayerConnect(playerid)<br />
{<br />
    // Create a map for each player on connect<br />
    MapInventory[playerid] = Map_Create();<br />
    return 1;<br />
}<br />
<br />
public OnPlayerDisconnect(playerid, reason)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
<br />
    // Check validity before deleting<br />
    if (Map_IsValid(mapid))<br />
    {<br />
        // Fully destroy the map and free the memory in the plugin<br />
        Map_Destroy(mapid);<br />
        <br />
        // Reset the variable to its initial state<br />
        MapInventory[playerid] = INVALID_MAP_ID;<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock GiveInventoryItems(playerid, itemid, amount, const name[])<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    // Check whether this item already exists in the inventory (by the Item field)<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    static data[e_inventory];<br />
<br />
    if (slotid != INVALID_MAP_KEY_ID)<br />
    {<br />
        // If the item is found - increase the amount<br />
        Map_Get(mapid, slotid, data);<br />
        data[Amount] += amount;<br />
        Map_Set(mapid, slotid, data);<br />
    }<br />
    else<br />
    {<br />
        // If the item is new - check the slot limit<br />
        if (Map_GetKeyCount(mapid) &gt;= INVENTORY_MAX_SLOTS)<br />
        {<br />
            SendClientMessage(playerid, -1, "No free slot for the item.");<br />
            return 0;<br />
        }<br />
<br />
        new free_slotid = Map_GetFreeKey(mapid);<br />
        if (free_slotid == INVALID_MAP_KEY_ID)<br />
        {<br />
            SendClientMessage(playerid, -1, "Error finding a free slot.");<br />
            return 0;<br />
        }<br />
<br />
        data[Item] = itemid;<br />
        data[Amount] = amount;<br />
        Map_SetString(data[Name], name);<br />
<br />
        Map_Set(mapid, free_slotid, data);<br />
    }<br />
    SendClientMessage(playerid, -1, "Inventory updated.");<br />
    return 1;<br />
}<br />
<br />
stock RemoveInventoryItems(playerid, itemid)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    if (slotid == INVALID_MAP_KEY_ID)<br />
    {<br />
        SendClientMessage(playerid, -1, "Error: item not found.");<br />
        return 0;<br />
    }<br />
    Map_RemoveKey(mapid, slotid);<br />
    SendClientMessage(playerid, -1, "Item removed.");<br />
    return 1;<br />
}<br />
<br />
stock UseInventoryItems(playerid, itemid, amount)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new slotid = Map_FindKeyByField(mapid, e_inventory:Item, MAP_TYPE_INT, itemid);<br />
<br />
    if (slotid == INVALID_MAP_KEY_ID)<br />
    {<br />
        SendClientMessage(playerid, -1, "Error: item not found.");<br />
        return 0;<br />
    }<br />
<br />
    static data[e_inventory];<br />
    Map_Get(mapid, slotid, data);<br />
<br />
    data[Amount] -= amount;<br />
<br />
    if (data[Amount] &lt;= 0)<br />
    {<br />
        Map_RemoveKey(mapid, slotid);<br />
    }<br />
    else<br />
    {<br />
        Map_Set(mapid, slotid, data);<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock SwapInventoryItems(playerid, slot_1, slot_2)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
<br />
    if (Map_Swap(mapid, slot_1, slot_2))<br />
    {<br />
        SendClientMessage(playerid, -1, "Items successfully moved.");<br />
    }<br />
    else<br />
    {<br />
        SendClientMessage(playerid, -1, "Move error.");<br />
    }<br />
    return 1;<br />
}<br />
<br />
stock ShowInventory(playerid)<br />
{<br />
    new PawnMap:mapid = MapInventory[playerid];<br />
    new string[1024];<br />
    <br />
    mapfor(mapid, slotid)<br />
    {<br />
        static data[e_inventory];<br />
        Map_Get(mapid, slotid, data);<br />
<br />
        format(string, sizeof(string),<br />
            "%s№%d&#92;t%s&#92;tAmount: %d&#92;n",<br />
            string,<br />
            slotid,<br />
            data[Name],<br />
            data[Amount]<br />
        );<br />
    }<br />
    ShowPlayerDialog(playerid, 1000, DIALOG_STYLE_LIST, "Inventory", string, "Select", "Close");<br />
    return 1;<br />
}<br />
<br />
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])<br />
{<br />
    if (dialogid == 1000)<br />
    {<br />
        if (!response) return 0;<br />
<br />
        new slotid = listitem;<br />
        new PawnMap:mapid = MapInventory[playerid];<br />
<br />
        static data[e_inventory];<br />
        if (Map_Get(mapid, slotid, data))<br />
        {<br />
            new string[144];<br />
            format(string, sizeof(string),<br />
                "You selected - Name: %s | ID: %d | Amount: %d",<br />
                data[Name], data[Item], data[Amount]<br />
            );<br />
            SendClientMessage(playerid, -1, string);<br />
        }<br />
    }<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b">Speed test </span><br />
<br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr>
<th class="tcat" align="middle"><strong>OPERATION</strong></th>
<th class="tcat" align="middle"><strong>PawnMap</strong></th>
<th class="tcat" align="middle"><strong>Raw Array</strong></th>
<th class="tcat" align="middle"><strong>DIFF (RA/PM)</strong></th>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CREATE &amp; DEL</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">x0.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SET</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">x0.5</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">GET</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">2 ms</td>
<td class="trow1" valign="top" align="center">x2.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">ADD</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">x0.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">FIND</td>
<td class="trow1" valign="top" align="center">8 ms</td>
<td class="trow1" valign="top" align="center">417 ms</td>
<td class="trow1" valign="top" align="center">x52.1</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CONTAINS</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">1052 ms</td>
<td class="trow1" valign="top" align="center">x1052.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">REMOVE</td>
<td class="trow1" valign="top" align="center">44 ms</td>
<td class="trow1" valign="top" align="center">1901 ms</td>
<td class="trow1" valign="top" align="center">x43.2</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SWAP</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">4 ms</td>
<td class="trow1" valign="top" align="center">x4.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SORT</td>
<td class="trow1" valign="top" align="center">4 ms</td>
<td class="trow1" valign="top" align="center">18 ms</td>
<td class="trow1" valign="top" align="center">x4.5</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">SET STR</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">1 ms</td>
<td class="trow1" valign="top" align="center">x1.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center">CONS STR</td>
<td class="trow1" valign="top" align="center">0 ms</td>
<td class="trow1" valign="top" align="center">1394 ms</td>
<td class="trow1" valign="top" align="center">x1394.0</td>
</tr>
<tr>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">TOTAL TIME</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">63 ms</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">4790 ms</span></td>
<td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">x76.0</span></td>
</tr>
</table>
<br />
<blockquote class="mycode_quote"><cite>Quote:</cite>At this point, only an integer (int) can be a key. In four years of working on projects, I only needed a string identifier for a system once;I don't recall other cases where it would have been necessary. To avoid cluttering the API with duplicate functions with the _Str postfix (which would need to be added to practically every function) and to avoid reducing map performance, I decided not to implement string key support. Instead, I implemented the ability to convert a string into an integer key.</blockquote>
<br />
<span style="font-weight: bold;" class="mycode_b">Example</span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>enum inventory_struct<br />
{<br />
    Itemid,<br />
    Amount<br />
}<br />
<br />
public OnGameModeInit()<br />
{<br />
    new PawnMap:mapid = Map_Create();<br />
<br />
    // 1. Convert the string "Deagle" into a numeric ID and write the data<br />
    new keyid = Map_StringKeyToInt(mapid, "Deagle");<br />
<br />
    if (keyid != INVALID_MAP_KEY_ID)<br />
    {<br />
        new data[inventory_struct];<br />
<br />
        data[Itemid] = 24;<br />
        data[Amount] = 100;<br />
<br />
        // Save the data array under this ID<br />
        Map_Set(mapid, keyid, data);<br />
<br />
        printf("String 'Deagle' successfully converted to ID: %d", keyid);<br />
    }<br />
<br />
    // 2. Get the ID by string (using the already created variable, without 'new')<br />
    keyid = Map_GetIdByStringKey(mapid, "Deagle");<br />
<br />
    if (keyid != INVALID_MAP_KEY_ID)<br />
    {<br />
        printf("ID for key 'Deagle' found: %d", keyid);<br />
    }<br />
    else<br />
    {<br />
        printf("This string key does not exist in the map.");<br />
    }<br />
<br />
    // 3. Check for the key directly<br />
    if (Map_ContainsStringKey(mapid, "Deagle"))<br />
    {<br />
        printf("Key 'Deagle' exists in this map.");<br />
    }<br />
    else<br />
    {<br />
        printf("Key 'Deagle' not found.");<br />
    }<br />
<br />
    // 4. Convert the numeric ID back to a string<br />
    new buffer[32];<br />
<br />
    if (Map_GetStringById(mapid, keyid, buffer))<br />
    {<br />
        printf("Name of key under ID %d - '%s'", keyid, buffer);<br />
    }<br />
<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Download:</span> &gt;&gt; <a href="https://github.com/i-Saibot/PawnMap/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/i-Saibot/PawnMap/releases</a></span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Maxora]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4324</link>
			<pubDate>Thu, 16 Jul 2026 21:14:02 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8236">itskoleban</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4324</guid>
			<description><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Maxora</span></span><br />
<span style="font-style: italic;" class="mycode_i">Highly optimized, zero-allocation MaxMind DB component for open.mp</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Maxora</span> is a production-ready component designed specifically for modern open.mp servers. It allows you to natively query <a href="https://maxmind.github.io/MaxMind-DB/" target="_blank" rel="noopener" class="mycode_url">MaxMind DB (.mmdb)</a> databases directly from your Pawn scripts to instantly geolocate IPs (Country, City, ASN, ISP) without choking your main server thread.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Features</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Extreme Performance</span>: Uses Memory-Mapped I/O (<span style="font-family: courier;" class="mycode_font">MMDB_MODE_MMAP</span>) for instant <span style="font-family: courier;" class="mycode_font">O(depth)</span> lookups directly from the OS disk cache.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Zero Heap Allocations</span>: Hot paths run exclusively on the C++ stack using fixed-size buffers and <span style="font-family: courier;" class="mycode_font">thread_local</span> memory arrays. Performs <span style="font-weight: bold;" class="mycode_b">0 dynamic heap allocations</span> per query.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Memory Safety Guarantee</span>: Strictly enforces bounds checking and utilizes open.mp's native <span style="font-family: courier;" class="mycode_font">StringView</span> to prevent buffer overflows during AMX string interactions.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Atomic Hot-Swaps</span>: Reloads the database on the fly. If you provide an invalid file, the plugin gracefully rejects it while preserving the existing database, preventing live-server crashes.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dynamic JSON-like Paths</span>: Query any arbitrary depth hierarchy natively (e.g., <span style="font-family: courier;" class="mycode_font">"country.names.es"</span>).<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Usage Example</span></span><br />
Maxora comes with pre-made <span style="font-family: courier;" class="mycode_font">stock</span> helpers to simplify common queries and supports up to 8 localized languages out of the box!<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;maxora&gt;<br />
<br />
main() {}<br />
<br />
public OnGameModeInit() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;// The path resolves from your server's root directory.<br />
&nbsp;&nbsp;&nbsp;&nbsp;// You can place it anywhere, for example inside scriptfiles:<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (MMDB_Load("scriptfiles/GeoLite2-City.mmdb")) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("[MMDB] Database loaded successfully.");<br />
&nbsp;&nbsp;&nbsp;&nbsp;} else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new err[128];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MMDB_GetLastError(err, sizeof(err));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Failed to load database. Reason: %s", err);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br />
}<br />
<br />
public OnPlayerConnect(playerid) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;new ip[16], country[64];<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;GetPlayerIp(playerid, ip, sizeof(ip));<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;// Querying a localized English country name using stock helpers<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (MMDB_GetCountryName(ip, country, sizeof(country), MMDB_LANG_ENGLISH)) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Player %d connected | IP: %s | Country: %s", playerid, ip, country);<br />
&nbsp;&nbsp;&nbsp;&nbsp;} else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new err[128];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MMDB_GetLastError(err, sizeof(err));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Player %d connected | IP: %s | Country: Unknown (Reason: %s)", playerid, ip, err);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Download &amp; Installation</span></span><br />
<span style="font-weight: bold;" class="mycode_b">Requirements:</span> open.mp server (Windows or Linux x86).<br />
<ol type="1" class="mycode_list"><li>Download the latest <span style="font-weight: bold;" class="mycode_b">maxora.dll</span> or <span style="font-weight: bold;" class="mycode_b">libmaxora.so</span> from the <a href="https://github.com/itskoleban/Maxora/releases" target="_blank" rel="noopener" class="mycode_url">GitHub Releases Page</a>.<br />
</li>
<li>Place it in your open.mp <span style="font-family: courier;" class="mycode_font">components</span> directory.<br />
</li>
<li>Download a <a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data" target="_blank" rel="noopener" class="mycode_url">GeoLite2 Free Database</a> and place it in the server root.<br />
</li>
<li>Add <span style="font-family: courier;" class="mycode_font">maxora.inc</span> to your pawno/include folder.<br />
</li>
</ol>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Source Code</span></span><br />
Maxora is fully open-source and natively built against the modern <span style="font-family: courier;" class="mycode_font">IComponent</span> open.mp SDK interface.<br />
<a href="https://github.com/itskoleban/Maxora" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/Maxora</a>]]></description>
			<content:encoded><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Maxora</span></span><br />
<span style="font-style: italic;" class="mycode_i">Highly optimized, zero-allocation MaxMind DB component for open.mp</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Maxora</span> is a production-ready component designed specifically for modern open.mp servers. It allows you to natively query <a href="https://maxmind.github.io/MaxMind-DB/" target="_blank" rel="noopener" class="mycode_url">MaxMind DB (.mmdb)</a> databases directly from your Pawn scripts to instantly geolocate IPs (Country, City, ASN, ISP) without choking your main server thread.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Features</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Extreme Performance</span>: Uses Memory-Mapped I/O (<span style="font-family: courier;" class="mycode_font">MMDB_MODE_MMAP</span>) for instant <span style="font-family: courier;" class="mycode_font">O(depth)</span> lookups directly from the OS disk cache.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Zero Heap Allocations</span>: Hot paths run exclusively on the C++ stack using fixed-size buffers and <span style="font-family: courier;" class="mycode_font">thread_local</span> memory arrays. Performs <span style="font-weight: bold;" class="mycode_b">0 dynamic heap allocations</span> per query.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Memory Safety Guarantee</span>: Strictly enforces bounds checking and utilizes open.mp's native <span style="font-family: courier;" class="mycode_font">StringView</span> to prevent buffer overflows during AMX string interactions.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Atomic Hot-Swaps</span>: Reloads the database on the fly. If you provide an invalid file, the plugin gracefully rejects it while preserving the existing database, preventing live-server crashes.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dynamic JSON-like Paths</span>: Query any arbitrary depth hierarchy natively (e.g., <span style="font-family: courier;" class="mycode_font">"country.names.es"</span>).<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Usage Example</span></span><br />
Maxora comes with pre-made <span style="font-family: courier;" class="mycode_font">stock</span> helpers to simplify common queries and supports up to 8 localized languages out of the box!<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;maxora&gt;<br />
<br />
main() {}<br />
<br />
public OnGameModeInit() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;// The path resolves from your server's root directory.<br />
&nbsp;&nbsp;&nbsp;&nbsp;// You can place it anywhere, for example inside scriptfiles:<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (MMDB_Load("scriptfiles/GeoLite2-City.mmdb")) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("[MMDB] Database loaded successfully.");<br />
&nbsp;&nbsp;&nbsp;&nbsp;} else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new err[128];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MMDB_GetLastError(err, sizeof(err));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Failed to load database. Reason: %s", err);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br />
}<br />
<br />
public OnPlayerConnect(playerid) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;new ip[16], country[64];<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;GetPlayerIp(playerid, ip, sizeof(ip));<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;// Querying a localized English country name using stock helpers<br />
&nbsp;&nbsp;&nbsp;&nbsp;if (MMDB_GetCountryName(ip, country, sizeof(country), MMDB_LANG_ENGLISH)) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Player %d connected | IP: %s | Country: %s", playerid, ip, country);<br />
&nbsp;&nbsp;&nbsp;&nbsp;} else {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new err[128];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MMDB_GetLastError(err, sizeof(err));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("[MMDB] Player %d connected | IP: %s | Country: Unknown (Reason: %s)", playerid, ip, err);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Download &amp; Installation</span></span><br />
<span style="font-weight: bold;" class="mycode_b">Requirements:</span> open.mp server (Windows or Linux x86).<br />
<ol type="1" class="mycode_list"><li>Download the latest <span style="font-weight: bold;" class="mycode_b">maxora.dll</span> or <span style="font-weight: bold;" class="mycode_b">libmaxora.so</span> from the <a href="https://github.com/itskoleban/Maxora/releases" target="_blank" rel="noopener" class="mycode_url">GitHub Releases Page</a>.<br />
</li>
<li>Place it in your open.mp <span style="font-family: courier;" class="mycode_font">components</span> directory.<br />
</li>
<li>Download a <a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data" target="_blank" rel="noopener" class="mycode_url">GeoLite2 Free Database</a> and place it in the server root.<br />
</li>
<li>Add <span style="font-family: courier;" class="mycode_font">maxora.inc</span> to your pawno/include folder.<br />
</li>
</ol>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Source Code</span></span><br />
Maxora is fully open-source and natively built against the modern <span style="font-family: courier;" class="mycode_font">IComponent</span> open.mp SDK interface.<br />
<a href="https://github.com/itskoleban/Maxora" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/Maxora</a>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[SAMP-TTS]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4322</link>
			<pubDate>Tue, 14 Jul 2026 17:31:41 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8206">connyxv</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4322</guid>
			<description><![CDATA[Hey guys. I've been working on a plugin that brings real Text-To-Speech to SA-MP using the ElevenLabs API, and I decided to release the first version.<br />
<br />
Instead of freezing the server while downloading the audio, the plugin runs entirely on a separate thread and uses a small Python backend to cache the voice lines. This means zero lag for the game mode and you save a lot of API tokens.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Main features:</span><br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">3D Positional Audio</span>: You can attach the voice to a specific location or an NPC. The volume fades naturally as you walk away.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Background Preload</span>: You can generate the voice lines silently when the server starts. When you need them, they play instantly with no delay.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Duration callbacks</span>: The plugin calculates exactly how long the audio file is in seconds and sends it to the PAWN callback. It's really useful if you want to sync an NPC talking animation with the voice.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dynamic voice tuning</span>: You can change the AI model, stability, and similarity directly from PAWN to simulate different tones.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">How to deal with "Audio Stream" messages</span><br />
As you probably know, the SA-MP client prints "Audio stream: URL" in the chat every time an audio starts. We can't block this from the server. Your players will need to type <span style="font-style: italic;" class="mycode_i">/audiomsg</span> in game to hide those texts. I suggest putting a reminder in your server rules or tutorial.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Download &amp; Source</span><br />
You can find the Windows DLL, the Linux .so, the Python server files and a clean example gamemode on GitHub:<br />
<a href="https://github.com/connysamp/SAMP-TTS" target="_blank" rel="noopener" class="mycode_url">GitHub Repository</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Example video</span><br />
I also recorded the plugin in action, you can take a look here:<br />
<a href="https://streamable.com/fxb0y2" target="_blank" rel="noopener" class="mycode_url">https://streamable.com/fxb0y2</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Important</span><br />
Also remember that the free version of ElevenLabs got limitations on wich voices you can use, so stick with the voices that you find in the "Default" section, to get the ID of a voice click on the dots beside voice and click "Copy voice ID".<br />
Also it's better using Eleven Flash 2.5 as it's 50% cheaper (it's default in the script) and it's faster if you need to say things without delay.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">List of voices you can use in the free plan:</span><br />
Bella - hpp4J3VqNfWAUOO0d1Us<br />
Roger - CwhRBWXzGAHq8TQ4Fs17<br />
Sarah - EXAVITQu4vr4xnSDxMaL<br />
Laura - FGY2WhTYpPnrIDTdsKH5<br />
Charlie - IKne3meq5aSn9XLyUdCD<br />
George - JBFqnCBsd6RMkjVDRZzb<br />
Callum - N2lVS1w4EtoT3dr4eOWO<br />
River - SAz9YHcvj6GT2YYXdXww<br />
Harry - SOYHLrjzK2X1ezoPC6cr<br />
Liam - TX3LPaxmHKxFdv7VOQHJ<br />
Alice - Xb7hH8MSUJpSbSDYk0k2<br />
Matilda - XrExE9yKIg1WjnnlVkGX<br />
Will - bIHbv24MWmeRgasZH58o<br />
Jessica - cgSgspJ2msm6clMCkdW9<br />
Eric - cjVigY5qzO86Huf0OWal<br />
Chris - iP95p4xoKVk53GoZ742B<br />
Brian - nPczCjzI2devNBz1zQrb<br />
Daniel - onwK4e9ZLuTAKqWW03F9<br />
Lily - pFZP5JQG7iQjIQuC4Bku<br />
Adam - pNInz6obpgDQGcFmaJgB<br />
Bill - pqHfZKP75CvOlQylNhV4<br />
<br />
Let me know if you find any bugs or need help.]]></description>
			<content:encoded><![CDATA[Hey guys. I've been working on a plugin that brings real Text-To-Speech to SA-MP using the ElevenLabs API, and I decided to release the first version.<br />
<br />
Instead of freezing the server while downloading the audio, the plugin runs entirely on a separate thread and uses a small Python backend to cache the voice lines. This means zero lag for the game mode and you save a lot of API tokens.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Main features:</span><br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">3D Positional Audio</span>: You can attach the voice to a specific location or an NPC. The volume fades naturally as you walk away.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Background Preload</span>: You can generate the voice lines silently when the server starts. When you need them, they play instantly with no delay.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Duration callbacks</span>: The plugin calculates exactly how long the audio file is in seconds and sends it to the PAWN callback. It's really useful if you want to sync an NPC talking animation with the voice.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Dynamic voice tuning</span>: You can change the AI model, stability, and similarity directly from PAWN to simulate different tones.<br />
</li>
</ul>
<br />
<span style="font-weight: bold;" class="mycode_b">How to deal with "Audio Stream" messages</span><br />
As you probably know, the SA-MP client prints "Audio stream: URL" in the chat every time an audio starts. We can't block this from the server. Your players will need to type <span style="font-style: italic;" class="mycode_i">/audiomsg</span> in game to hide those texts. I suggest putting a reminder in your server rules or tutorial.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Download &amp; Source</span><br />
You can find the Windows DLL, the Linux .so, the Python server files and a clean example gamemode on GitHub:<br />
<a href="https://github.com/connysamp/SAMP-TTS" target="_blank" rel="noopener" class="mycode_url">GitHub Repository</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Example video</span><br />
I also recorded the plugin in action, you can take a look here:<br />
<a href="https://streamable.com/fxb0y2" target="_blank" rel="noopener" class="mycode_url">https://streamable.com/fxb0y2</a><br />
<br />
<span style="font-weight: bold;" class="mycode_b">Important</span><br />
Also remember that the free version of ElevenLabs got limitations on wich voices you can use, so stick with the voices that you find in the "Default" section, to get the ID of a voice click on the dots beside voice and click "Copy voice ID".<br />
Also it's better using Eleven Flash 2.5 as it's 50% cheaper (it's default in the script) and it's faster if you need to say things without delay.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">List of voices you can use in the free plan:</span><br />
Bella - hpp4J3VqNfWAUOO0d1Us<br />
Roger - CwhRBWXzGAHq8TQ4Fs17<br />
Sarah - EXAVITQu4vr4xnSDxMaL<br />
Laura - FGY2WhTYpPnrIDTdsKH5<br />
Charlie - IKne3meq5aSn9XLyUdCD<br />
George - JBFqnCBsd6RMkjVDRZzb<br />
Callum - N2lVS1w4EtoT3dr4eOWO<br />
River - SAz9YHcvj6GT2YYXdXww<br />
Harry - SOYHLrjzK2X1ezoPC6cr<br />
Liam - TX3LPaxmHKxFdv7VOQHJ<br />
Alice - Xb7hH8MSUJpSbSDYk0k2<br />
Matilda - XrExE9yKIg1WjnnlVkGX<br />
Will - bIHbv24MWmeRgasZH58o<br />
Jessica - cgSgspJ2msm6clMCkdW9<br />
Eric - cjVigY5qzO86Huf0OWal<br />
Chris - iP95p4xoKVk53GoZ742B<br />
Brian - nPczCjzI2devNBz1zQrb<br />
Daniel - onwK4e9ZLuTAKqWW03F9<br />
Lily - pFZP5JQG7iQjIQuC4Bku<br />
Adam - pNInz6obpgDQGcFmaJgB<br />
Bill - pqHfZKP75CvOlQylNhV4<br />
<br />
Let me know if you find any bugs or need help.]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[IndexWeaver]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4321</link>
			<pubDate>Tue, 14 Jul 2026 00:56:18 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8236">itskoleban</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4321</guid>
			<description><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">IndexWeaver</span></span><br />
<span style="font-style: italic;" class="mycode_i">A high-performance, lightweight in-memory ID-to-index registry plugin designed specifically for open.mp.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- The Problem</span></span><br />
How many times have you written a linear `for` loop across `MAX_PLAYERS` or `MAX_VEHICLES` just to find the array slot that matches a specific SQL Database ID or an arbitrary key? Linear loops `O(N)` are slow and unnecessary.<br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- The Solution</span></span><br />
<span style="font-weight: bold;" class="mycode_b">IndexWeaver</span> completely eliminates these linear searches. It maps your primary keys (like SQL IDs) directly to your Pawn array slots, allowing your Pawn code to remain the single source of truth for the actual data, while giving you instantaneous <span style="font-weight: bold;" class="mycode_b">O(1)</span> memory lookups.<br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Architecture &amp; Performance</span></span><br />
IndexWeaver implements a heavily optimized <span style="font-weight: bold;" class="mycode_b">Hybrid Storage Model</span>:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Fast Registries (Type 0 - 63):</span> Uses a fixed contiguous array. Erasing elements retains capacity (Zero-Allocation Retention), ensuring that frequent re-insertions (like players reconnecting) never trigger expensive OS heap allocations.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Slow Registries (Type &gt;= 64):</span> Uses `robin_hood` flat maps as a dynamic fallback. It features aggressive cleanup; when a registry drops to zero elements, it is safely destroyed to prevent memory leaks in long-running servers.<br />
</li>
</ul>
<span style="font-style: italic;" class="mycode_i">Benchmarks (MSVC /O2): 10 Million Insertions in ~0.92s, 10 Million Lookups in ~0.62s!</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Pawn API</span></span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>native bool:SetMapIndex(registry_type, id, index);<br />
native GetMapIndex(registry_type, id);<br />
native bool:RemoveMapIndex(registry_type, id);<br />
native bool:HasMapIndex(registry_type, id);<br />
native bool:ClearMapRegistry(registry_type);<br />
native ClearAllMapRegistries();<br />
native bool:ReserveMapRegistry(registry_type, capacity);<br />
native bool:SetMapDebugLevel(level);</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Quick Example</span></span><br />
Instead of looping through all loaded clans to find the one with `SQL_ID == 850`:<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>// Store the index when loading from the database<br />
SetMapIndex(REGISTRY_CLANS, 850, array_slot);<br />
<br />
// Later, instantly retrieve the slot! No loops!<br />
new slot = GetMapIndex(REGISTRY_CLANS, 850);<br />
if(slot != INVALID_MAP_INDEX) {<br />
    printf("Clan Name: %s", ClanData[slot][cName]);<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Download &amp; Source Code</span></span><br />
The plugin is entirely open-source, beautifully documented, and cross-platform (Windows/Linux).<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">GitHub Repository:</span> <a href="https://github.com/itskoleban/IndexWeaver" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/IndexWeaver</a><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Releases &amp; Binaries:</span> <a href="https://github.com/itskoleban/IndexWeaver/releases" target="_blank" rel="noopener" class="mycode_url">Download .dll and .so here</a><br />
</li>
</ul>
<br />
<span style="font-style: italic;" class="mycode_i">Contributions and Pull Requests are welcome!</span>]]></description>
			<content:encoded><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">IndexWeaver</span></span><br />
<span style="font-style: italic;" class="mycode_i">A high-performance, lightweight in-memory ID-to-index registry plugin designed specifically for open.mp.</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- The Problem</span></span><br />
How many times have you written a linear `for` loop across `MAX_PLAYERS` or `MAX_VEHICLES` just to find the array slot that matches a specific SQL Database ID or an arbitrary key? Linear loops `O(N)` are slow and unnecessary.<br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- The Solution</span></span><br />
<span style="font-weight: bold;" class="mycode_b">IndexWeaver</span> completely eliminates these linear searches. It maps your primary keys (like SQL IDs) directly to your Pawn array slots, allowing your Pawn code to remain the single source of truth for the actual data, while giving you instantaneous <span style="font-weight: bold;" class="mycode_b">O(1)</span> memory lookups.<br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Architecture &amp; Performance</span></span><br />
IndexWeaver implements a heavily optimized <span style="font-weight: bold;" class="mycode_b">Hybrid Storage Model</span>:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Fast Registries (Type 0 - 63):</span> Uses a fixed contiguous array. Erasing elements retains capacity (Zero-Allocation Retention), ensuring that frequent re-insertions (like players reconnecting) never trigger expensive OS heap allocations.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Slow Registries (Type &gt;= 64):</span> Uses `robin_hood` flat maps as a dynamic fallback. It features aggressive cleanup; when a registry drops to zero elements, it is safely destroyed to prevent memory leaks in long-running servers.<br />
</li>
</ul>
<span style="font-style: italic;" class="mycode_i">Benchmarks (MSVC /O2): 10 Million Insertions in ~0.92s, 10 Million Lookups in ~0.62s!</span><br />
<br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Pawn API</span></span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>native bool:SetMapIndex(registry_type, id, index);<br />
native GetMapIndex(registry_type, id);<br />
native bool:RemoveMapIndex(registry_type, id);<br />
native bool:HasMapIndex(registry_type, id);<br />
native bool:ClearMapRegistry(registry_type);<br />
native ClearAllMapRegistries();<br />
native bool:ReserveMapRegistry(registry_type, capacity);<br />
native bool:SetMapDebugLevel(level);</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Quick Example</span></span><br />
Instead of looping through all loaded clans to find the one with `SQL_ID == 850`:<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>// Store the index when loading from the database<br />
SetMapIndex(REGISTRY_CLANS, 850, array_slot);<br />
<br />
// Later, instantly retrieve the slot! No loops!<br />
new slot = GetMapIndex(REGISTRY_CLANS, 850);<br />
if(slot != INVALID_MAP_INDEX) {<br />
    printf("Clan Name: %s", ClanData[slot][cName]);<br />
}</code></div></div><br />
<span style="font-weight: bold;" class="mycode_b"><span style="font-size: medium;" class="mycode_size">- Download &amp; Source Code</span></span><br />
The plugin is entirely open-source, beautifully documented, and cross-platform (Windows/Linux).<br />
<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">GitHub Repository:</span> <a href="https://github.com/itskoleban/IndexWeaver" target="_blank" rel="noopener" class="mycode_url">https://github.com/itskoleban/IndexWeaver</a><br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Releases &amp; Binaries:</span> <a href="https://github.com/itskoleban/IndexWeaver/releases" target="_blank" rel="noopener" class="mycode_url">Download .dll and .so here</a><br />
</li>
</ul>
<br />
<span style="font-style: italic;" class="mycode_i">Contributions and Pull Requests are welcome!</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Pedestrians]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4305</link>
			<pubDate>Thu, 25 Jun 2026 08:52:40 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=8206">connyxv</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4305</guid>
			<description><![CDATA[Hello everyone!<br />
<br />
I've been working on a new pedestrian system for SA-MP and open.mp. Most of the older ped scripts out there rely on traditional NPCs, which is kind of a pain because they eat up your player slots and i hate to see in the tablist "BOT_1" with the black color to hide it, they also can cause serious server lag if you try to spawn hundreds of them. <br />
<br />
To fix this, this system relies entirely on Actors. It uses a custom C++ plugin that loads the original GTA SA path nodes (i extracted them and put them in the pedpaths.json) and handles all the heavy lifting (math and background pathfinding) so the PAWN server doesn't choke. The include just handles streaming the actors dynamically around players using a recycled global pool.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Main Features:</span><br />
1. Uses zero player slots.<br />
2. Fast C++ pathfinding with predictive spawning (calculates where players are heading so peds spawn naturally ahead of you, preventing node clustering).<br />
3. Dynamic reactions: if you shoot a gun near them, they panic and sprint away. If you run them over with a vehicle, they fall to the ground, get back up after a few seconds, and resume their walk. <br />
4. If you aim at them with a weapon, they'll put their hands up (OnPlayerAimPedestrian, that's in the example script, u can ignore this function if not needed)<br />
5. Customizable skins and walking styles in the include.<br />
6. 100% open.mp compatible.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Source Code &amp; Download:</span><br />
<a href="https://github.com/connysamp/Pedestrians" target="_blank" rel="noopener" class="mycode_url">GitHub</a><br />
<br />
It's compiled for both Windows (.dll) and Linux (.so 32-bit). You'll find instructions on how to use it and compile it from source if u want. <br />
<br />
Feel free to test it out, fork it, or drop a PR if you want to contribute. Let me know if you run into any weird bugs!<br />
I am also working on another plugin, drivers.dll, i think in a week or two i will publish it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Video preview: </span>(sorry for the shitty music, that's audio added with the youtube editor so i was very limited)<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/ecZSkwN3gOE" frameborder="0" allowfullscreen="true"></iframe>]]></description>
			<content:encoded><![CDATA[Hello everyone!<br />
<br />
I've been working on a new pedestrian system for SA-MP and open.mp. Most of the older ped scripts out there rely on traditional NPCs, which is kind of a pain because they eat up your player slots and i hate to see in the tablist "BOT_1" with the black color to hide it, they also can cause serious server lag if you try to spawn hundreds of them. <br />
<br />
To fix this, this system relies entirely on Actors. It uses a custom C++ plugin that loads the original GTA SA path nodes (i extracted them and put them in the pedpaths.json) and handles all the heavy lifting (math and background pathfinding) so the PAWN server doesn't choke. The include just handles streaming the actors dynamically around players using a recycled global pool.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Main Features:</span><br />
1. Uses zero player slots.<br />
2. Fast C++ pathfinding with predictive spawning (calculates where players are heading so peds spawn naturally ahead of you, preventing node clustering).<br />
3. Dynamic reactions: if you shoot a gun near them, they panic and sprint away. If you run them over with a vehicle, they fall to the ground, get back up after a few seconds, and resume their walk. <br />
4. If you aim at them with a weapon, they'll put their hands up (OnPlayerAimPedestrian, that's in the example script, u can ignore this function if not needed)<br />
5. Customizable skins and walking styles in the include.<br />
6. 100% open.mp compatible.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Source Code &amp; Download:</span><br />
<a href="https://github.com/connysamp/Pedestrians" target="_blank" rel="noopener" class="mycode_url">GitHub</a><br />
<br />
It's compiled for both Windows (.dll) and Linux (.so 32-bit). You'll find instructions on how to use it and compile it from source if u want. <br />
<br />
Feel free to test it out, fork it, or drop a PR if you want to contribute. Let me know if you run into any weird bugs!<br />
I am also working on another plugin, drivers.dll, i think in a week or two i will publish it.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Video preview: </span>(sorry for the shitty music, that's audio added with the youtube editor so i was very limited)<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/ecZSkwN3gOE" frameborder="0" allowfullscreen="true"></iframe>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[VoiceChat] omp-voice]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4288</link>
			<pubDate>Sat, 13 Jun 2026 18:15:23 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=5142">ionuzcostin</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4288</guid>
			<description><![CDATA[[RELEASE] omp-voice — Proximity + Radio/Phone Voice Chat for open.mp &amp; SA-MP 0.3.7<br />
<hr class="mycode_hr" />
What is omp-voice?<br />
<span style="font-weight: bold;" class="mycode_b">omp-voice</span> is a full-featured, server-authoritative voice chat system for <span style="font-weight: bold;" class="mycode_b">open.mp</span> and <span style="font-weight: bold;" class="mycode_b">SA-MP 0.3.7 R5</span>. It brings <span style="font-weight: bold;" class="mycode_b">proximity voice</span>, <span style="font-weight: bold;" class="mycode_b">radio channels</span>, and <span style="font-weight: bold;" class="mycode_b">phone calls</span> to your server — all encrypted, low-latency, and scriptable via Pawn.<br />
GitHub: <a href="https://github.com/staark-dev/omp-voice" target="_blank" rel="noopener" class="mycode_url">https://github.com/staark-dev/omp-voice</a><br />
<hr class="mycode_hr" />
Features<br />
🎙️ Proximity Voice<ul class="mycode_list"><li>Hear nearby players in <span style="font-weight: bold;" class="mycode_b">3D audio</span> with logarithmic distance falloff and stereo panning<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Occlusion</span> support — voices are muffled through walls and closed vehicles<br />
</li>
<li>Three preset ranges: <span style="font-weight: bold;" class="mycode_b">Whisper / Normal / Shout</span>, or a custom range per player<br />
</li>
<li>Interior and virtual-world gating (you only hear players in the same dimension)<br />
</li>
</ul>
📻 Radio &amp; Phone Channels<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Radio filter</span>: bandpass + squelch + half-duplex garble for that authentic feel<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Phone filter</span>: narrowband colouring for realistic call audio<br />
</li>
<li>Mixed <span style="font-weight: bold;" class="mycode_b">per-listener</span> on the server for perfect control<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Speakerphone</span> (<br />
/vspeaker<br />
) — let bystanders hear a phone call out loud<br />
</li>
</ul>
🔒 Security<ul class="mycode_list"><li>Per-connect <span style="font-weight: bold;" class="mycode_b">cryptographic token</span> identity (not IP-based)<br />
</li>
<li>Every relay↔client datagram sealed with <span style="font-weight: bold;" class="mycode_b">XChaCha20-Poly1305</span> AEAD encryption<br />
</li>
<li>Server is fully authoritative for position, flags, and channels — anti-spoof<br />
</li>
</ul>
🎧 Audio Quality<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Opus codec</span> — industry-standard, low-latency voice compression<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">RNNoise</span> neural noise suppression (enable/disable per client)<br />
</li>
<li>Adaptive jitter buffer for smooth playback<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Push-to-Talk</span> or <span style="font-weight: bold;" class="mycode_b">Voice Activation</span> modes (exclusive per client)<br />
</li>
</ul>
🖥️ In-Game Client UI (ImGui)<ul class="mycode_list"><li>Press <span style="font-weight: bold;" class="mycode_b">INSERT</span> to open the control panel: volumes, devices, PTT key rebind, noise suppression<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">"Who's talking" HUD</span> (toggle with F8) — see active speakers on-screen<br />
</li>
<li>Optional <span style="font-weight: bold;" class="mycode_b">floating labels</span> above player heads while they talk<br />
</li>
<li>Per-player local mute and volume control<br />
</li>
</ul>
<hr class="mycode_hr" />
Architecture<br />
omp-voice uses a <span style="font-weight: bold;" class="mycode_b">hybrid mixing model</span>:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Proximity</span> audio is relayed per-speaker as raw Opus; the client spatialises it locally (lowest latency)<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Radio/Phone buses</span> are decoded, filtered, and mixed <span style="font-weight: bold;" class="mycode_b">server-side per listener</span>, then re-encoded and streamed<br />
</li>
</ul>
The system consists of six modules:<br />
Module<br />
Roleshared/<br />
Wire protocol, crypto (libsodium), INI, UDP<br />
core/<br />
Routing engine: proximity, bus router, channels, mix policy<br />
relay/<br />
Standalone relay process — AEAD transport shell<br />
samp/<br />
SA-MP 0.3.7 plugin bridge (Pawn natives)<br />
omp/<br />
open.mp component bridge<br />
client/<br />
Windows <br />
.asi<br />
— WASAPI, Opus, RNNoise, ImGui panel<br />
<hr class="mycode_hr" />
Installation<br />
Server Side<br />
<ol type="1" class="mycode_list"><li>Drop <br />
plugins/VoiceChat.dll<br />
 (SA-MP) or the open.mp component into <br />
components/<br />
</li>
<li>Place <br />
voice_relay.exe<br />
 alongside the server executable<br />
</li>
<li>Copy <br />
qawno/include/voicechat.inc<br />
 (+ <br />
voicechat_ui.inc<br />
) for your gamemode<br />
</li>
<li>Add <br />
voice.ini<br />
 (config file — annotated, comes with the release)<br />
</li>
<li>Add to <br />
config.json<br />
: <br />
"legacy_plugins": ["VoiceChat"]<br />
 or to <br />
server.cfg<br />
 for SA-MP<br />
</li>
</ol>
Client Side (each player)<br />
<ol type="1" class="mycode_list"><li>Place <br />
VoiceChat.asi<br />
 in the GTA SA folder (requires an ASI loader: SilentPatch, CLEO, etc.)<br />
</li>
<li>Edit <br />
voicechat.ini<br />
 — set <br />
relay_ip<br />
 to the server's IP address<br />
</li>
</ol>
Firewall<br />
Open <span style="font-weight: bold;" class="mycode_b">UDP port 7779</span> inbound on your server:<br />
# Windows (admin)<br />
netsh advfirewall firewall add rule name="VoiceChat 7779" dir=in action=allow protocol=UDP localport=7779<br />
<br />
# Linux<br />
sudo ufw allow 7779/udp<br />
<br />
<hr class="mycode_hr" />
Pawn API (quick overview)<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;voicechat&gt;<br />
#include &lt;voicechat_ui&gt;<br />
<br />
public OnGameModeInit() {<br />
    gProximity = Voice_CreateChannel(VOICE_FILTER_NONE, false);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerConnect(playerid) {<br />
    new ip[16]; GetPlayerIp(playerid, ip, sizeof ip);<br />
    Voice_OnPlayerConnect(playerid, ip);<br />
    Voice_AddToChannel(playerid, gProximity);<br />
    Voice_SetPlayerRange(playerid, VOICE_RANGE_NORMAL);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerUpdate(playerid) {<br />
    new Float:x, Float:y, Float:z;<br />
    GetPlayerPos(playerid, x, y, z);<br />
    new veh = GetPlayerVehicleID(playerid) ? VOICE_VEH_CLOSED : VOICE_ONFOOT;<br />
    Voice_UpdatePosition(playerid, x, y, z,<br />
        GetPlayerInterior(playerid), GetPlayerVirtualWorld(playerid), veh);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerStartTalking(playerid) { VoiceUI_OnStartTalking(playerid); return 1; }<br />
public OnPlayerStopTalking(playerid)  { VoiceUI_OnStopTalking(playerid);  return 1; }<br />
public OnPlayerDisconnect(playerid, reason) {<br />
    Voice_OnPlayerDisconnect(playerid);<br />
    return 1;<br />
}<br />
<br />
Key Natives<br />
// Channel management<br />
Voice_CreateChannel(filter, bool:positional, priority = 100);<br />
Voice_AddToChannel(playerid, channelid);<br />
Voice_RemoveFromChannel(playerid, channelid);<br />
<br />
// Player control<br />
Voice_SetPlayerRange(playerid, preset, Float:override = 0.0);<br />
Voice_SetPlayerFlags(playerid, bool:muted, bool:deafen, bool:alive, bool:spectator);<br />
Voice_SetMixPolicy(playerid, policy, Float:duckLevel = 0.5, Float:proximityFloor = 0.25);<br />
Voice_MutePlayer(playerid, targetid, bool:mute = true);  // per-pair mute<br />
Voice_SetSpeakerphone(playerid, bool:on = true);<br />
<br />
// Lifecycle<br />
Voice_OnPlayerConnect(playerid, const ip[]);<br />
Voice_OnPlayerDisconnect(playerid);<br />
Voice_ReloadConfig();  // reload voice.ini at runtime</code></div></div><br />
<br />
<hr class="mycode_hr" />
In-Game Controls (Client)<br />
Key Action:<br />
<span style="font-weight: bold;" class="mycode_b">B </span>Proximity push-to-talk<br />
<span style="font-weight: bold;" class="mycode_b">N </span>Radio/phone push-to-talk<br />
<span style="font-weight: bold;" class="mycode_b">F9 </span>Self-mute<br />
<span style="font-weight: bold;" class="mycode_b">F8 </span>Toggle "who's talking" HUD<br />
<span style="font-weight: bold;" class="mycode_b">INSERT </span>Open settings panel<br />
<br />
All keys are rebindable in the in-game panel.<br />
<hr class="mycode_hr" />
Building from Source<br />
<span style="font-weight: bold;" class="mycode_b">One-shot Windows build:</span><br />
powershell -ExecutionPolicy Bypass -File .\build.ps1<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Manual CMake (production config, x86):</span><br />
cmake -S . -B build -A Win32 -DVC_NO_SODIUM=OFF -DVC_WITH_OPUS=ON -DVC_BUILD_CLIENT=ON<br />
cmake --build build --config Release<br />
<br />
All third-party dependencies (libsodium, Opus, Dear ImGui, MinHook, RNNoise) are fetched and built automatically by CMake — no manual installs needed.<br />
CI/CD via GitHub Actions builds and tests on every push. Tagged releases (v*) publish ready-to-use bundles automatically.<br />
<hr class="mycode_hr" />
Current Status<br />
Core engine and all major features are implemented and validated in-game (2-player proximity with occlusion + falloff, radio/phone channels, speakerphone, RNNoise, ImGui panel).<br />
<span style="font-weight: bold;" class="mycode_b">Remaining / planned:</span><ul class="mycode_list"><li>AEC (Acoustic Echo Cancellation) — deferred; headphones avoid the issue for now<br />
</li>
<li>Linux 32-bit.so for SA-MP (relay and open.mp component already build on Linux x64)<br />
</li>
</ul>
<hr class="mycode_hr" />
Download<br />
👉 <span style="font-weight: bold;" class="mycode_b">GitHub Releases:</span> <a href="https://github.com/staark-dev/omp-voice/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/staark-dev/omp-voice/releases</a><br />
<br />
Bundles available:<ul class="mycode_list"><li>voicechat-client-win32.zip<br />
 — client.asi + voicechat.ini<br />
</li>
<li>voicechat-server-win.zip<br />
 — server plugin + relay + includes + voice.ini<br />
</li>
<li>voicechat-relay-linux-x64.tar.gz<br />
 — relay binary for Linux servers<br />
</li>
</ul>
Each bundle includes an <br />
INSTALL.txt.<br />
<hr class="mycode_hr" />
License<br />
MIT — free to use, modify, and distribute. See <a href="https://github.com/staark-dev/omp-voice/blob/main/LICENSE" target="_blank" rel="noopener" class="mycode_url">LICENSE</a>.<br />
<hr class="mycode_hr" />
<span style="font-style: italic;" class="mycode_i">Feedback, bug reports, and pull requests are welcome on GitHub!</span>]]></description>
			<content:encoded><![CDATA[[RELEASE] omp-voice — Proximity + Radio/Phone Voice Chat for open.mp &amp; SA-MP 0.3.7<br />
<hr class="mycode_hr" />
What is omp-voice?<br />
<span style="font-weight: bold;" class="mycode_b">omp-voice</span> is a full-featured, server-authoritative voice chat system for <span style="font-weight: bold;" class="mycode_b">open.mp</span> and <span style="font-weight: bold;" class="mycode_b">SA-MP 0.3.7 R5</span>. It brings <span style="font-weight: bold;" class="mycode_b">proximity voice</span>, <span style="font-weight: bold;" class="mycode_b">radio channels</span>, and <span style="font-weight: bold;" class="mycode_b">phone calls</span> to your server — all encrypted, low-latency, and scriptable via Pawn.<br />
GitHub: <a href="https://github.com/staark-dev/omp-voice" target="_blank" rel="noopener" class="mycode_url">https://github.com/staark-dev/omp-voice</a><br />
<hr class="mycode_hr" />
Features<br />
🎙️ Proximity Voice<ul class="mycode_list"><li>Hear nearby players in <span style="font-weight: bold;" class="mycode_b">3D audio</span> with logarithmic distance falloff and stereo panning<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Occlusion</span> support — voices are muffled through walls and closed vehicles<br />
</li>
<li>Three preset ranges: <span style="font-weight: bold;" class="mycode_b">Whisper / Normal / Shout</span>, or a custom range per player<br />
</li>
<li>Interior and virtual-world gating (you only hear players in the same dimension)<br />
</li>
</ul>
📻 Radio &amp; Phone Channels<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Radio filter</span>: bandpass + squelch + half-duplex garble for that authentic feel<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Phone filter</span>: narrowband colouring for realistic call audio<br />
</li>
<li>Mixed <span style="font-weight: bold;" class="mycode_b">per-listener</span> on the server for perfect control<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Speakerphone</span> (<br />
/vspeaker<br />
) — let bystanders hear a phone call out loud<br />
</li>
</ul>
🔒 Security<ul class="mycode_list"><li>Per-connect <span style="font-weight: bold;" class="mycode_b">cryptographic token</span> identity (not IP-based)<br />
</li>
<li>Every relay↔client datagram sealed with <span style="font-weight: bold;" class="mycode_b">XChaCha20-Poly1305</span> AEAD encryption<br />
</li>
<li>Server is fully authoritative for position, flags, and channels — anti-spoof<br />
</li>
</ul>
🎧 Audio Quality<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Opus codec</span> — industry-standard, low-latency voice compression<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">RNNoise</span> neural noise suppression (enable/disable per client)<br />
</li>
<li>Adaptive jitter buffer for smooth playback<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Push-to-Talk</span> or <span style="font-weight: bold;" class="mycode_b">Voice Activation</span> modes (exclusive per client)<br />
</li>
</ul>
🖥️ In-Game Client UI (ImGui)<ul class="mycode_list"><li>Press <span style="font-weight: bold;" class="mycode_b">INSERT</span> to open the control panel: volumes, devices, PTT key rebind, noise suppression<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">"Who's talking" HUD</span> (toggle with F8) — see active speakers on-screen<br />
</li>
<li>Optional <span style="font-weight: bold;" class="mycode_b">floating labels</span> above player heads while they talk<br />
</li>
<li>Per-player local mute and volume control<br />
</li>
</ul>
<hr class="mycode_hr" />
Architecture<br />
omp-voice uses a <span style="font-weight: bold;" class="mycode_b">hybrid mixing model</span>:<ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Proximity</span> audio is relayed per-speaker as raw Opus; the client spatialises it locally (lowest latency)<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Radio/Phone buses</span> are decoded, filtered, and mixed <span style="font-weight: bold;" class="mycode_b">server-side per listener</span>, then re-encoded and streamed<br />
</li>
</ul>
The system consists of six modules:<br />
Module<br />
Roleshared/<br />
Wire protocol, crypto (libsodium), INI, UDP<br />
core/<br />
Routing engine: proximity, bus router, channels, mix policy<br />
relay/<br />
Standalone relay process — AEAD transport shell<br />
samp/<br />
SA-MP 0.3.7 plugin bridge (Pawn natives)<br />
omp/<br />
open.mp component bridge<br />
client/<br />
Windows <br />
.asi<br />
— WASAPI, Opus, RNNoise, ImGui panel<br />
<hr class="mycode_hr" />
Installation<br />
Server Side<br />
<ol type="1" class="mycode_list"><li>Drop <br />
plugins/VoiceChat.dll<br />
 (SA-MP) or the open.mp component into <br />
components/<br />
</li>
<li>Place <br />
voice_relay.exe<br />
 alongside the server executable<br />
</li>
<li>Copy <br />
qawno/include/voicechat.inc<br />
 (+ <br />
voicechat_ui.inc<br />
) for your gamemode<br />
</li>
<li>Add <br />
voice.ini<br />
 (config file — annotated, comes with the release)<br />
</li>
<li>Add to <br />
config.json<br />
: <br />
"legacy_plugins": ["VoiceChat"]<br />
 or to <br />
server.cfg<br />
 for SA-MP<br />
</li>
</ol>
Client Side (each player)<br />
<ol type="1" class="mycode_list"><li>Place <br />
VoiceChat.asi<br />
 in the GTA SA folder (requires an ASI loader: SilentPatch, CLEO, etc.)<br />
</li>
<li>Edit <br />
voicechat.ini<br />
 — set <br />
relay_ip<br />
 to the server's IP address<br />
</li>
</ol>
Firewall<br />
Open <span style="font-weight: bold;" class="mycode_b">UDP port 7779</span> inbound on your server:<br />
# Windows (admin)<br />
netsh advfirewall firewall add rule name="VoiceChat 7779" dir=in action=allow protocol=UDP localport=7779<br />
<br />
# Linux<br />
sudo ufw allow 7779/udp<br />
<br />
<hr class="mycode_hr" />
Pawn API (quick overview)<br />
<br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;voicechat&gt;<br />
#include &lt;voicechat_ui&gt;<br />
<br />
public OnGameModeInit() {<br />
    gProximity = Voice_CreateChannel(VOICE_FILTER_NONE, false);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerConnect(playerid) {<br />
    new ip[16]; GetPlayerIp(playerid, ip, sizeof ip);<br />
    Voice_OnPlayerConnect(playerid, ip);<br />
    Voice_AddToChannel(playerid, gProximity);<br />
    Voice_SetPlayerRange(playerid, VOICE_RANGE_NORMAL);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerUpdate(playerid) {<br />
    new Float:x, Float:y, Float:z;<br />
    GetPlayerPos(playerid, x, y, z);<br />
    new veh = GetPlayerVehicleID(playerid) ? VOICE_VEH_CLOSED : VOICE_ONFOOT;<br />
    Voice_UpdatePosition(playerid, x, y, z,<br />
        GetPlayerInterior(playerid), GetPlayerVirtualWorld(playerid), veh);<br />
    return 1;<br />
}<br />
<br />
public OnPlayerStartTalking(playerid) { VoiceUI_OnStartTalking(playerid); return 1; }<br />
public OnPlayerStopTalking(playerid)  { VoiceUI_OnStopTalking(playerid);  return 1; }<br />
public OnPlayerDisconnect(playerid, reason) {<br />
    Voice_OnPlayerDisconnect(playerid);<br />
    return 1;<br />
}<br />
<br />
Key Natives<br />
// Channel management<br />
Voice_CreateChannel(filter, bool:positional, priority = 100);<br />
Voice_AddToChannel(playerid, channelid);<br />
Voice_RemoveFromChannel(playerid, channelid);<br />
<br />
// Player control<br />
Voice_SetPlayerRange(playerid, preset, Float:override = 0.0);<br />
Voice_SetPlayerFlags(playerid, bool:muted, bool:deafen, bool:alive, bool:spectator);<br />
Voice_SetMixPolicy(playerid, policy, Float:duckLevel = 0.5, Float:proximityFloor = 0.25);<br />
Voice_MutePlayer(playerid, targetid, bool:mute = true);  // per-pair mute<br />
Voice_SetSpeakerphone(playerid, bool:on = true);<br />
<br />
// Lifecycle<br />
Voice_OnPlayerConnect(playerid, const ip[]);<br />
Voice_OnPlayerDisconnect(playerid);<br />
Voice_ReloadConfig();  // reload voice.ini at runtime</code></div></div><br />
<br />
<hr class="mycode_hr" />
In-Game Controls (Client)<br />
Key Action:<br />
<span style="font-weight: bold;" class="mycode_b">B </span>Proximity push-to-talk<br />
<span style="font-weight: bold;" class="mycode_b">N </span>Radio/phone push-to-talk<br />
<span style="font-weight: bold;" class="mycode_b">F9 </span>Self-mute<br />
<span style="font-weight: bold;" class="mycode_b">F8 </span>Toggle "who's talking" HUD<br />
<span style="font-weight: bold;" class="mycode_b">INSERT </span>Open settings panel<br />
<br />
All keys are rebindable in the in-game panel.<br />
<hr class="mycode_hr" />
Building from Source<br />
<span style="font-weight: bold;" class="mycode_b">One-shot Windows build:</span><br />
powershell -ExecutionPolicy Bypass -File .\build.ps1<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Manual CMake (production config, x86):</span><br />
cmake -S . -B build -A Win32 -DVC_NO_SODIUM=OFF -DVC_WITH_OPUS=ON -DVC_BUILD_CLIENT=ON<br />
cmake --build build --config Release<br />
<br />
All third-party dependencies (libsodium, Opus, Dear ImGui, MinHook, RNNoise) are fetched and built automatically by CMake — no manual installs needed.<br />
CI/CD via GitHub Actions builds and tests on every push. Tagged releases (v*) publish ready-to-use bundles automatically.<br />
<hr class="mycode_hr" />
Current Status<br />
Core engine and all major features are implemented and validated in-game (2-player proximity with occlusion + falloff, radio/phone channels, speakerphone, RNNoise, ImGui panel).<br />
<span style="font-weight: bold;" class="mycode_b">Remaining / planned:</span><ul class="mycode_list"><li>AEC (Acoustic Echo Cancellation) — deferred; headphones avoid the issue for now<br />
</li>
<li>Linux 32-bit.so for SA-MP (relay and open.mp component already build on Linux x64)<br />
</li>
</ul>
<hr class="mycode_hr" />
Download<br />
👉 <span style="font-weight: bold;" class="mycode_b">GitHub Releases:</span> <a href="https://github.com/staark-dev/omp-voice/releases" target="_blank" rel="noopener" class="mycode_url">https://github.com/staark-dev/omp-voice/releases</a><br />
<br />
Bundles available:<ul class="mycode_list"><li>voicechat-client-win32.zip<br />
 — client.asi + voicechat.ini<br />
</li>
<li>voicechat-server-win.zip<br />
 — server plugin + relay + includes + voice.ini<br />
</li>
<li>voicechat-relay-linux-x64.tar.gz<br />
 — relay binary for Linux servers<br />
</li>
</ul>
Each bundle includes an <br />
INSTALL.txt.<br />
<hr class="mycode_hr" />
License<br />
MIT — free to use, modify, and distribute. See <a href="https://github.com/staark-dev/omp-voice/blob/main/LICENSE" target="_blank" rel="noopener" class="mycode_url">LICENSE</a>.<br />
<hr class="mycode_hr" />
<span style="font-style: italic;" class="mycode_i">Feedback, bug reports, and pull requests are welcome on GitHub!</span>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[MySQL for open.mp]]></title>
			<link>https://forum.open.mp/showthread.php?tid=4274</link>
			<pubDate>Mon, 01 Jun 2026 21:17:55 +0000</pubDate>
			<dc:creator><![CDATA[<a href="https://forum.open.mp/member.php?action=profile&uid=4409">Xyranaut</a>]]></dc:creator>
			<guid isPermaLink="false">https://forum.open.mp/showthread.php?tid=4274</guid>
			<description><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></span><br />
A modern, secure MySQL database component for open.mp — clean-room, with an industry-standard API.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Repo:</span> <a href="https://github.com/Mac-Andreas/omp-MySQL" target="_blank" rel="noopener" class="mycode_url">github.com/Mac-Andreas/omp-MySQL</a><br />
<span style="font-weight: bold;" class="mycode_b">Download (v1.0.0):</span> <a href="https://github.com/Mac-Andreas/omp-MySQL/releases/latest" target="_blank" rel="noopener" class="mycode_url">Releases</a><br />
<span style="font-weight: bold;" class="mycode_b">Docs / wiki:</span> <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki" target="_blank" rel="noopener" class="mycode_url">Wiki</a><br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">What is it?</span></span><br />
A native open.mp <span style="font-weight: bold;" class="mycode_b">component</span> (drops into <span style="font-family: Courier New;" class="mycode_font">components/</span>, not a legacy plugin) that lets your Pawn gamemode talk to MySQL — safely. It's inspired by the classic SA-MP-MySQL but rebuilt from scratch for modern MySQL and the open.mp Component SDK.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Why another MySQL plugin?</span></span><br />
SA-MP-MySQL (BlueG / maddinat0r) was excellent — for its time. That time was the MySQL 5.x / SA-MP era, and it's been <span style="font-weight: bold;" class="mycode_b">discontinued since ~2019</span>. Modern MySQL changed a lot (default <span style="font-family: Courier New;" class="mycode_font">caching_sha2_password</span> auth, mandatory-friendly TLS, utf8mb4), open.mp moved to a modern Component SDK, and security expectations rose. omp-MySQL is a clean-room project built for <span style="font-weight: bold;" class="mycode_b">today's</span> MySQL and open.mp: TLS is on by default, passwords are hashed for you, and player input is injection-safe by construction.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">omp-MySQL vs SA-MP-MySQL (BlueG)</span></span><br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr><td class="trow1" valign="top" align="center"></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">SA-MP-MySQL (BlueG)</span></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></td></tr>
<tr><td class="trow1" valign="top" align="center">Plugs into</td><td class="trow1" valign="top" align="center">SA-MP plugin ABI</td><td class="trow1" valign="top" align="center">native open.mp Component SDK</td></tr>
<tr><td class="trow1" valign="top" align="center">MySQL era</td><td class="trow1" valign="top" align="center">5.x / MariaDB</td><td class="trow1" valign="top" align="center">modern 8.x / 9.x (also runs on 5.7)</td></tr>
<tr><td class="trow1" valign="top" align="center">Encryption</td><td class="trow1" valign="top" align="center">optional, often off</td><td class="trow1" valign="top" align="center">mandatory TLS, fail-closed (1.3/1.2)</td></tr>
<tr><td class="trow1" valign="top" align="center">Password hashing</td><td class="trow1" valign="top" align="center">none built in</td><td class="trow1" valign="top" align="center">Argon2id (mysql_hash / mysql_verify)</td></tr>
<tr><td class="trow1" valign="top" align="center">Injection safety</td><td class="trow1" valign="top" align="center">escape helpers</td><td class="trow1" valign="top" align="center">prepared statements + %e + multi-stmt off</td></tr>
<tr><td class="trow1" valign="top" align="center">API naming</td><td class="trow1" valign="top" align="center">SA-MP-MySQL specific</td><td class="trow1" valign="top" align="center">industry-standard (JDBC/DB-API style)</td></tr>
<tr><td class="trow1" valign="top" align="center">Secrets</td><td class="trow1" valign="top" align="center">usually in source</td><td class="trow1" valign="top" align="center">&#36;{ENV} expansion + optional obfuscation</td></tr>
<tr><td class="trow1" valign="top" align="center">Extras</td><td class="trow1" valign="top" align="center">cache, ORM</td><td class="trow1" valign="top" align="center">cache, models, VECTOR search, compression</td></tr>
<tr><td class="trow1" valign="top" align="center">Status</td><td class="trow1" valign="top" align="center">discontinued (~2019)</td><td class="trow1" valign="top" align="center">active, v1.0.0</td></tr>
</table>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Highlights</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Mandatory TLS, fail-closed</span> — TLS 1.3 (MySQL 8.0+) / TLS 1.2 (5.7); refuses to connect unencrypted. No plaintext path.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Prepared statements</span> — bound parameters, injection-safe (plus <span style="font-family: Courier New;" class="mycode_font">%e</span> escaping; multi-statements off by default).<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Argon2id password hashing</span> built in.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Fully async</span> — one worker thread per connection; the main tick never blocks.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Self-contained</span> — MariaDB Connector/C statically linked, OpenSSL bundled. No separate client install.<br />
</li>
<li>Result cache, active-record <span style="font-weight: bold;" class="mycode_b">models</span>, MySQL 9 <span style="font-weight: bold;" class="mycode_b">VECTOR</span> similarity search, <span style="font-family: Courier New;" class="mycode_font">&#36;{ENV}</span> secrets, opt-in rate/length guards.<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Tested</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Live MySQL</span>: 5.7.44, 8.0.46, 8.4.9 LTS, 9.2.0 — TLS + queries + prepared statements + caching_sha2 all pass. VECTOR verified on 9.x (it's 9.0+ only).<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Memory</span>: clean under AddressSanitizer + UBSan; static leak/UAF/thread audit.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Pentested</span>: SQL injection (login box, registration, command args), auth bypass (rejoin / slot reuse), persistent-login enforcement, privilege bootstrap. Details in the wiki's Security page.<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Quick example</span></span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;omp-mysql&gt;<br />
<br />
new MySQL:g_DB;<br />
<br />
public OnGameModeInit()<br />
{<br />
    new MySQLConfig:cfg = mysql_config_create();<br />
    mysql_config_set(cfg, SSL_MODE, SSL_MODE_REQUIRED);  // TLS on<br />
    g_DB = mysql_connect("127.0.0.1", "user", "&#36;{OMP_DB_PASS}", "mydb", cfg);<br />
    if (g_DB == MYSQL_INVALID_HANDLE) print("MySQL connection failed.");<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Install</span></span><br />
<ol type="1" class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Windows:</span> unzip the windows package — <span style="font-family: Courier New;" class="mycode_font">omp-mysql.dll</span> into <span style="font-family: Courier New;" class="mycode_font">components/</span>, and the bundled <span style="font-family: Courier New;" class="mycode_font">libssl-3.dll</span> / <span style="font-family: Courier New;" class="mycode_font">libcrypto-3.dll</span> next to <span style="font-family: Courier New;" class="mycode_font">omp-server.exe</span>.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Linux:</span> <span style="font-family: Courier New;" class="mycode_font">omp-mysql.so</span> into <span style="font-family: Courier New;" class="mycode_font">components/</span>.<br />
</li>
<li>Put <span style="font-family: Courier New;" class="mycode_font">omp-mysql.inc</span> in <span style="font-family: Courier New;" class="mycode_font">qawno/include/</span> and <span style="font-family: Courier New;" class="mycode_font">#include &lt;omp-mysql&gt;</span>.<br />
</li>
</ol>
A full <span style="font-weight: bold;" class="mycode_b">login/register admin demo</span> (<span style="font-family: Courier New;" class="mycode_font">mysql-admin</span>) ships in the release.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Migrating from SA-MP-MySQL</span></span><br />
Same concepts (connect → query → callback → read rows); modern/standard names. Most-used mappings:<br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">SA-MP-MySQL</span></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_tquery</td><td class="trow1" valign="top" align="center">mysql_execute</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_pquery</td><td class="trow1" valign="top" align="center">mysql_execute_for</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_query</td><td class="trow1" valign="top" align="center">mysql_execute_sync</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_get_value_name_int</td><td class="trow1" valign="top" align="center">mysql_rs_get_int_by</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_num_rows</td><td class="trow1" valign="top" align="center">mysql_rs_row_count</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_insert_id</td><td class="trow1" valign="top" align="center">mysql_rs_insert_id</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_get_ssl_cipher</td><td class="trow1" valign="top" align="center">mysql_get_tls_cipher</td></tr>
<tr><td class="trow1" valign="top" align="center">(none)</td><td class="trow1" valign="top" align="center">mysql_prepare / mysql_stmt_set_*</td></tr>
<tr><td class="trow1" valign="top" align="center">(none)</td><td class="trow1" valign="top" align="center">mysql_hash / mysql_verify</td></tr>
<tr><td class="trow1" valign="top" align="center">orm_*</td><td class="trow1" valign="top" align="center">mysql_model_*</td></tr>
</table>
Two convention changes: prepared-statement params are <span style="font-weight: bold;" class="mycode_b">1-based</span>, and result values use the <span style="font-family: Courier New;" class="mycode_font">mysql_rs_*</span> family. Full guide: <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki/Migrating-from-SA-MP-MySQL" target="_blank" rel="noopener" class="mycode_url">Migration</a>.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Docs</span></span><br />
The <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki" target="_blank" rel="noopener" class="mycode_url">wiki</a> is a full book — from "what is a database?" (with diagrams) through installing MySQL, SQL basics, prepared statements, async patterns, the mysql-admin demo, building from source, and security. There's a one-page <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki/Cheat-Sheet" target="_blank" rel="noopener" class="mycode_url">cheat sheet</a> too.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Notes</span></span><ul class="mycode_list"><li>Minimum MySQL <span style="font-weight: bold;" class="mycode_b">5.7</span> (first with default TLS). Recommended <span style="font-weight: bold;" class="mycode_b">8.4 LTS</span>.<br />
</li>
<li>32-bit (i386) — the open.mp server arch.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">MIT</span> licensed (links the LGPL-2.1 MariaDB Connector/C). No code reused from SA-MP-MySQL.<br />
</li>
</ul>
<br />
Feedback, bug reports, and PRs welcome — <a href="https://github.com/Mac-Andreas/omp-MySQL/issues" target="_blank" rel="noopener" class="mycode_url">issues</a>.<br />
<br />
<span style="font-size: small;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">omp-MySQL is an independent community project — not affiliated with or endorsed by open.mp or Oracle. "MySQL" is a trademark of Oracle; "open.mp" and "SA-MP" of their respective authors; used here only to describe compatibility.</span></span>]]></description>
			<content:encoded><![CDATA[<span style="font-size: large;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></span><br />
A modern, secure MySQL database component for open.mp — clean-room, with an industry-standard API.<br />
<br />
<span style="font-weight: bold;" class="mycode_b">Repo:</span> <a href="https://github.com/Mac-Andreas/omp-MySQL" target="_blank" rel="noopener" class="mycode_url">github.com/Mac-Andreas/omp-MySQL</a><br />
<span style="font-weight: bold;" class="mycode_b">Download (v1.0.0):</span> <a href="https://github.com/Mac-Andreas/omp-MySQL/releases/latest" target="_blank" rel="noopener" class="mycode_url">Releases</a><br />
<span style="font-weight: bold;" class="mycode_b">Docs / wiki:</span> <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki" target="_blank" rel="noopener" class="mycode_url">Wiki</a><br />
<br />
<hr class="mycode_hr" />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">What is it?</span></span><br />
A native open.mp <span style="font-weight: bold;" class="mycode_b">component</span> (drops into <span style="font-family: Courier New;" class="mycode_font">components/</span>, not a legacy plugin) that lets your Pawn gamemode talk to MySQL — safely. It's inspired by the classic SA-MP-MySQL but rebuilt from scratch for modern MySQL and the open.mp Component SDK.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Why another MySQL plugin?</span></span><br />
SA-MP-MySQL (BlueG / maddinat0r) was excellent — for its time. That time was the MySQL 5.x / SA-MP era, and it's been <span style="font-weight: bold;" class="mycode_b">discontinued since ~2019</span>. Modern MySQL changed a lot (default <span style="font-family: Courier New;" class="mycode_font">caching_sha2_password</span> auth, mandatory-friendly TLS, utf8mb4), open.mp moved to a modern Component SDK, and security expectations rose. omp-MySQL is a clean-room project built for <span style="font-weight: bold;" class="mycode_b">today's</span> MySQL and open.mp: TLS is on by default, passwords are hashed for you, and player input is injection-safe by construction.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">omp-MySQL vs SA-MP-MySQL (BlueG)</span></span><br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr><td class="trow1" valign="top" align="center"></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">SA-MP-MySQL (BlueG)</span></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></td></tr>
<tr><td class="trow1" valign="top" align="center">Plugs into</td><td class="trow1" valign="top" align="center">SA-MP plugin ABI</td><td class="trow1" valign="top" align="center">native open.mp Component SDK</td></tr>
<tr><td class="trow1" valign="top" align="center">MySQL era</td><td class="trow1" valign="top" align="center">5.x / MariaDB</td><td class="trow1" valign="top" align="center">modern 8.x / 9.x (also runs on 5.7)</td></tr>
<tr><td class="trow1" valign="top" align="center">Encryption</td><td class="trow1" valign="top" align="center">optional, often off</td><td class="trow1" valign="top" align="center">mandatory TLS, fail-closed (1.3/1.2)</td></tr>
<tr><td class="trow1" valign="top" align="center">Password hashing</td><td class="trow1" valign="top" align="center">none built in</td><td class="trow1" valign="top" align="center">Argon2id (mysql_hash / mysql_verify)</td></tr>
<tr><td class="trow1" valign="top" align="center">Injection safety</td><td class="trow1" valign="top" align="center">escape helpers</td><td class="trow1" valign="top" align="center">prepared statements + %e + multi-stmt off</td></tr>
<tr><td class="trow1" valign="top" align="center">API naming</td><td class="trow1" valign="top" align="center">SA-MP-MySQL specific</td><td class="trow1" valign="top" align="center">industry-standard (JDBC/DB-API style)</td></tr>
<tr><td class="trow1" valign="top" align="center">Secrets</td><td class="trow1" valign="top" align="center">usually in source</td><td class="trow1" valign="top" align="center">&#36;{ENV} expansion + optional obfuscation</td></tr>
<tr><td class="trow1" valign="top" align="center">Extras</td><td class="trow1" valign="top" align="center">cache, ORM</td><td class="trow1" valign="top" align="center">cache, models, VECTOR search, compression</td></tr>
<tr><td class="trow1" valign="top" align="center">Status</td><td class="trow1" valign="top" align="center">discontinued (~2019)</td><td class="trow1" valign="top" align="center">active, v1.0.0</td></tr>
</table>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Highlights</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Mandatory TLS, fail-closed</span> — TLS 1.3 (MySQL 8.0+) / TLS 1.2 (5.7); refuses to connect unencrypted. No plaintext path.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Prepared statements</span> — bound parameters, injection-safe (plus <span style="font-family: Courier New;" class="mycode_font">%e</span> escaping; multi-statements off by default).<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Argon2id password hashing</span> built in.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Fully async</span> — one worker thread per connection; the main tick never blocks.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Self-contained</span> — MariaDB Connector/C statically linked, OpenSSL bundled. No separate client install.<br />
</li>
<li>Result cache, active-record <span style="font-weight: bold;" class="mycode_b">models</span>, MySQL 9 <span style="font-weight: bold;" class="mycode_b">VECTOR</span> similarity search, <span style="font-family: Courier New;" class="mycode_font">&#36;{ENV}</span> secrets, opt-in rate/length guards.<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Tested</span></span><ul class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Live MySQL</span>: 5.7.44, 8.0.46, 8.4.9 LTS, 9.2.0 — TLS + queries + prepared statements + caching_sha2 all pass. VECTOR verified on 9.x (it's 9.0+ only).<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Memory</span>: clean under AddressSanitizer + UBSan; static leak/UAF/thread audit.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Pentested</span>: SQL injection (login box, registration, command args), auth bypass (rejoin / slot reuse), persistent-login enforcement, privilege bootstrap. Details in the wiki's Security page.<br />
</li>
</ul>
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Quick example</span></span><br />
<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;open.mp&gt;<br />
#include &lt;omp-mysql&gt;<br />
<br />
new MySQL:g_DB;<br />
<br />
public OnGameModeInit()<br />
{<br />
    new MySQLConfig:cfg = mysql_config_create();<br />
    mysql_config_set(cfg, SSL_MODE, SSL_MODE_REQUIRED);  // TLS on<br />
    g_DB = mysql_connect("127.0.0.1", "user", "&#36;{OMP_DB_PASS}", "mydb", cfg);<br />
    if (g_DB == MYSQL_INVALID_HANDLE) print("MySQL connection failed.");<br />
    return 1;<br />
}</code></div></div><br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Install</span></span><br />
<ol type="1" class="mycode_list"><li><span style="font-weight: bold;" class="mycode_b">Windows:</span> unzip the windows package — <span style="font-family: Courier New;" class="mycode_font">omp-mysql.dll</span> into <span style="font-family: Courier New;" class="mycode_font">components/</span>, and the bundled <span style="font-family: Courier New;" class="mycode_font">libssl-3.dll</span> / <span style="font-family: Courier New;" class="mycode_font">libcrypto-3.dll</span> next to <span style="font-family: Courier New;" class="mycode_font">omp-server.exe</span>.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">Linux:</span> <span style="font-family: Courier New;" class="mycode_font">omp-mysql.so</span> into <span style="font-family: Courier New;" class="mycode_font">components/</span>.<br />
</li>
<li>Put <span style="font-family: Courier New;" class="mycode_font">omp-mysql.inc</span> in <span style="font-family: Courier New;" class="mycode_font">qawno/include/</span> and <span style="font-family: Courier New;" class="mycode_font">#include &lt;omp-mysql&gt;</span>.<br />
</li>
</ol>
A full <span style="font-weight: bold;" class="mycode_b">login/register admin demo</span> (<span style="font-family: Courier New;" class="mycode_font">mysql-admin</span>) ships in the release.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Migrating from SA-MP-MySQL</span></span><br />
Same concepts (connect → query → callback → read rows); modern/standard names. Most-used mappings:<br />
<table border="0" cellspacing="1" cellpadding="3" class="tborder" style="width:%;">
<tr><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">SA-MP-MySQL</span></td><td class="trow1" valign="top" align="center"><span style="font-weight: bold;" class="mycode_b">omp-MySQL</span></td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_tquery</td><td class="trow1" valign="top" align="center">mysql_execute</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_pquery</td><td class="trow1" valign="top" align="center">mysql_execute_for</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_query</td><td class="trow1" valign="top" align="center">mysql_execute_sync</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_get_value_name_int</td><td class="trow1" valign="top" align="center">mysql_rs_get_int_by</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_num_rows</td><td class="trow1" valign="top" align="center">mysql_rs_row_count</td></tr>
<tr><td class="trow1" valign="top" align="center">cache_insert_id</td><td class="trow1" valign="top" align="center">mysql_rs_insert_id</td></tr>
<tr><td class="trow1" valign="top" align="center">mysql_get_ssl_cipher</td><td class="trow1" valign="top" align="center">mysql_get_tls_cipher</td></tr>
<tr><td class="trow1" valign="top" align="center">(none)</td><td class="trow1" valign="top" align="center">mysql_prepare / mysql_stmt_set_*</td></tr>
<tr><td class="trow1" valign="top" align="center">(none)</td><td class="trow1" valign="top" align="center">mysql_hash / mysql_verify</td></tr>
<tr><td class="trow1" valign="top" align="center">orm_*</td><td class="trow1" valign="top" align="center">mysql_model_*</td></tr>
</table>
Two convention changes: prepared-statement params are <span style="font-weight: bold;" class="mycode_b">1-based</span>, and result values use the <span style="font-family: Courier New;" class="mycode_font">mysql_rs_*</span> family. Full guide: <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki/Migrating-from-SA-MP-MySQL" target="_blank" rel="noopener" class="mycode_url">Migration</a>.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Docs</span></span><br />
The <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki" target="_blank" rel="noopener" class="mycode_url">wiki</a> is a full book — from "what is a database?" (with diagrams) through installing MySQL, SQL basics, prepared statements, async patterns, the mysql-admin demo, building from source, and security. There's a one-page <a href="https://github.com/Mac-Andreas/omp-MySQL/wiki/Cheat-Sheet" target="_blank" rel="noopener" class="mycode_url">cheat sheet</a> too.<br />
<br />
<span style="font-size: medium;" class="mycode_size"><span style="font-weight: bold;" class="mycode_b">Notes</span></span><ul class="mycode_list"><li>Minimum MySQL <span style="font-weight: bold;" class="mycode_b">5.7</span> (first with default TLS). Recommended <span style="font-weight: bold;" class="mycode_b">8.4 LTS</span>.<br />
</li>
<li>32-bit (i386) — the open.mp server arch.<br />
</li>
<li><span style="font-weight: bold;" class="mycode_b">MIT</span> licensed (links the LGPL-2.1 MariaDB Connector/C). No code reused from SA-MP-MySQL.<br />
</li>
</ul>
<br />
Feedback, bug reports, and PRs welcome — <a href="https://github.com/Mac-Andreas/omp-MySQL/issues" target="_blank" rel="noopener" class="mycode_url">issues</a>.<br />
<br />
<span style="font-size: small;" class="mycode_size"><span style="font-style: italic;" class="mycode_i">omp-MySQL is an independent community project — not affiliated with or endorsed by open.mp or Oracle. "MySQL" is a trademark of Oracle; "open.mp" and "SA-MP" of their respective authors; used here only to describe compatibility.</span></span>]]></content:encoded>
		</item>
	</channel>
</rss>