Add fakemeta-basics/en.md
Browse Source
1 changed file
with 99 additions and 0 deletions
fakemeta-basics/en.md
A
| @@ -0,0 +1,99 @@ | ||
| 1 | +--- | |
| 2 | +title: "Fakemeta Basics: pev, set_pev and Engine Forwards" | |
| 3 | +excerpt: "Read and write entity variables with pev/set_pev, call engine and DLL functions, and hook engine forwards like FM_PlayerPreThink and FM_EmitSound." | |
| 4 | +category: "plugin-dev" | |
| 5 | +tags: "fakemeta, amxmodx" | |
| 6 | +image: "" | |
| 7 | +read_time: "7 min read" | |
| 8 | +--- | |
| 9 | +<p>Fakemeta (module <code>fakemeta</code>) gives AMXX plugins the same low-level access to the engine that a Metamod plugin written in C++ would have: it reads and writes the <code>entvars_t</code> struct of any entity, calls engine and game-DLL functions directly, and hooks the engine's forwards. It is fast and it is everywhere, but it is also unforgiving — there are no named members and no safety rails. This guide covers the three things you will use constantly: <code>pev</code>/<code>set_pev</code>, <code>engfunc</code>/<code>dllfunc</code>, and <code>register_forward</code>.</p> | |
| 10 | + | |
| 11 | +<h2>1. Reading entity variables with pev</h2> | |
| 12 | +<p>Every entity — players, weapons, grenades, doors — has an <code>entvars_t</code> block. <code>pev(index, member)</code> reads a field. Integer fields return directly; vector fields are read into a passed array; string fields into a buffer:</p> | |
| 13 | +<pre>#include <amxmodx> | |
| 14 | +#include <fakemeta> | |
| 15 | + | |
| 16 | +public show_state(id) | |
| 17 | +{ | |
| 18 | + new Float:health = pev(id, pev_health) | |
| 19 | + new flags = pev(id, pev_flags) | |
| 20 | + new team = pev(id, pev_team) | |
| 21 | + | |
| 22 | + new Float:origin[3] | |
| 23 | + pev(id, pev_origin, origin) | |
| 24 | + | |
| 25 | + new classname[32] | |
| 26 | + pev(id, pev_classname, classname, charsmax(classname)) | |
| 27 | + | |
| 28 | + server_print("hp=%.0f onground=%d team=%d at %.0f %.0f %.0f", | |
| 29 | + health, (flags & FL_ONGROUND) ? 1 : 0, team, | |
| 30 | + origin[0], origin[1], origin[2]) | |
| 31 | +}</pre> | |
| 32 | +<p>The <code>pev_*</code> constants are the field names. The ones you will reach for most are <code>pev_origin</code>, <code>pev_velocity</code>, <code>pev_health</code>, <code>pev_maxspeed</code>, <code>pev_flags</code>, <code>pev_button</code>, <code>pev_team</code>, <code>pev_frags</code>, <code>pev_deadflag</code>, and the generic user fields <code>pev_iuser1</code>..<code>pev_iuser4</code>.</p> | |
| 33 | + | |
| 34 | +<h2>2. Writing with set_pev</h2> | |
| 35 | +<p><code>set_pev</code> mirrors <code>pev</code>. Set a scalar directly, a vector from an array:</p> | |
| 36 | +<pre>public buff_player(id) | |
| 37 | +{ | |
| 38 | + set_pev(id, pev_health, 150.0) | |
| 39 | + set_pev(id, pev_maxspeed, 320.0) | |
| 40 | + | |
| 41 | + new Float:vel[3] | |
| 42 | + pev(id, pev_velocity, vel) | |
| 43 | + vel[2] = 300.0 // launch upward | |
| 44 | + set_pev(id, pev_velocity, vel) | |
| 45 | +}</pre> | |
| 46 | +<p>Note the types: <code>pev_health</code> and <code>pev_maxspeed</code> are floats, so pass <code>150.0</code>, not <code>150</code>. Writing an integer into a float member is a tag-mismatch warning and produces nonsense in-game. When you move a player, prefer setting velocity or using the engine's <code>SetOrigin</code> (below) over writing <code>pev_origin</code> directly, which can push a player into a wall.</p> | |
| 47 | + | |
| 48 | +<h2>3. engfunc and dllfunc</h2> | |
| 49 | +<p><code>engfunc</code> calls an engine function; <code>dllfunc</code> calls a game-DLL function. Both take a constant naming the function followed by its arguments:</p> | |
| 50 | +<pre>// teleport safely through the engine | |
| 51 | +new Float:dest[3] = { 0.0, 0.0, 128.0 } | |
| 52 | +engfunc(EngFunc_SetOrigin, id, dest) | |
| 53 | + | |
| 54 | +// give the player a weapon through the game DLL | |
| 55 | +dllfunc(DLLFunc_GiveNamedItem, id, "weapon_ak47") | |
| 56 | + | |
| 57 | +// slay a player cleanly | |
| 58 | +dllfunc(DLLFunc_ClientKill, id)</pre> | |
| 59 | +<p>These call the same functions the game itself calls, so side effects (sounds, prediction, physics) happen correctly — unlike poking <code>entvars_t</code> by hand. The full list of <code>EngFunc_*</code> and <code>DLLFunc_*</code> constants is in <code>fakemeta.inc</code>; read the include for exact argument orders rather than guessing.</p> | |
| 60 | + | |
| 61 | +<h2>4. Engine forwards</h2> | |
| 62 | +<p>The real power of fakemeta is <code>register_forward</code>: it hooks an engine function so your code runs every time the engine calls it. <code>FM_PlayerPreThink</code> runs each frame per player, <code>FM_Touch</code> when two entities touch, <code>FM_EmitSound</code> whenever a sound plays:</p> | |
| 63 | +<pre>public plugin_init() | |
| 64 | +{ | |
| 65 | + register_plugin("Fakemeta Demo", "1.0", "CSB") | |
| 66 | + register_forward(FM_PlayerPreThink, "fw_prethink") | |
| 67 | + register_forward(FM_EmitSound, "fw_emitsound") | |
| 68 | +} | |
| 69 | + | |
| 70 | +public fw_prethink(id) | |
| 71 | +{ | |
| 72 | + if (!is_user_alive(id)) | |
| 73 | + return FMRES_IGNORED | |
| 74 | + | |
| 75 | + // enforce a speed cap every frame | |
| 76 | + set_pev(id, pev_maxspeed, 300.0) | |
| 77 | + return FMRES_IGNORED | |
| 78 | +} | |
| 79 | + | |
| 80 | +public fw_emitsound(id, channel, const sample[]) | |
| 81 | +{ | |
| 82 | + // block footstep sounds | |
| 83 | + if (containi(sample, "player/pl_step") != -1) | |
| 84 | + return FMRES_SUPERCEDE | |
| 85 | + | |
| 86 | + return FMRES_IGNORED | |
| 87 | +}</pre> | |
| 88 | +<p>The return value controls what happens next. <code>FMRES_IGNORED</code> lets the engine proceed normally. <code>FMRES_SUPERCEDE</code> blocks the original call entirely — that is how you silence a sound or cancel a touch. <code>FMRES_HANDLED</code> marks it handled without blocking. Registering a forward as a <em>post</em> hook (pass <code>1</code> as the last argument to <code>register_forward</code>) runs your code after the engine, when you want the result rather than the chance to block it.</p> | |
| 89 | + | |
| 90 | +<h2>Common errors</h2> | |
| 91 | +<ul> | |
| 92 | +<li><strong>Tag mismatch on <code>pev_health</code> / <code>pev_origin</code></strong> — you used an integer where a float is required. Vector and health fields are <code>Float:</code>.</li> | |
| 93 | +<li><strong><code>FM_PlayerPreThink</code> tanks the server</strong> — it fires every frame for every player. Do the absolute minimum inside it, and bail early with <code>is_user_alive</code>. Heavy work here is the classic cause of <a href="/tutorials/fix-low-server-fps-linux">low server FPS</a>.</li> | |
| 94 | +<li><strong>Blocking a forward does nothing</strong> — you returned <code>FMRES_IGNORED</code>. Only <code>FMRES_SUPERCEDE</code> stops the original call.</li> | |
| 95 | +<li><strong>Player stuck in geometry after moving</strong> — you wrote <code>pev_origin</code> directly. Use <code>engfunc(EngFunc_SetOrigin, ...)</code> so the engine relinks the entity.</li> | |
| 96 | +</ul> | |
| 97 | + | |
| 98 | +<h2>Verification</h2> | |
| 99 | +<p>Print a value you set and read it back the next frame to confirm the write stuck: set <code>pev_maxspeed</code> in <code>FM_PlayerPreThink</code>, then check in-game that movement speed actually changed. For blocking hooks, trigger the sound or touch and confirm <code>FMRES_SUPERCEDE</code> suppressed it. When you need to <em>modify</em> damage or spawning rather than engine calls, Ham Sandwich is the better tool — see <a href="/tutorials/hamsandwich-basics">the Ham Sandwich guide</a> — and on ReGameDLL, <a href="/tutorials/reapi-getting-started">ReAPI</a> replaces most raw fakemeta with named members.</p> | |
| 0 | 100 |









