Add hamsandwich-basics/en.md
Browse Source
1 changed file
with 90 additions and 0 deletions
hamsandwich-basics/en.md
A
| @@ -0,0 +1,90 @@ | ||
| 1 | +--- | |
| 2 | +title: "Hamsandwich Guide: RegisterHam and Ham_TakeDamage" | |
| 3 | +excerpt: "Hook the game DLL's virtual functions with RegisterHam, read and modify parameters, block calls with HAM_SUPERCEDE, and use pre versus post correctly." | |
| 4 | +category: "plugin-dev" | |
| 5 | +tags: "hamsandwich, amxmodx" | |
| 6 | +image: "" | |
| 7 | +read_time: "7 min read" | |
| 8 | +--- | |
| 9 | +<p>Ham Sandwich (module <code>hamsandwich</code>) hooks the virtual methods of Half-Life/CS entities — the C++ functions the game DLL calls internally to spawn a player, apply damage, or handle a kill. Unlike a network event, a Ham hook runs <em>inside</em> the call, so you can read the real arguments, change them, and cancel the whole thing. It works on both stock CS and ReGameDLL, which makes it the portable way to alter core gameplay. <code>Ham_TakeDamage</code> is the one you will use most, so this guide builds around it.</p> | |
| 10 | + | |
| 11 | +<h2>1. Registering a hook</h2> | |
| 12 | +<p><code>RegisterHam</code> takes the function to hook, the entity classname whose method you want, your callback, and a post flag (<code>0</code> = pre, <code>1</code> = post). Register in <code>plugin_init()</code>:</p> | |
| 13 | +<pre>#include <amxmodx> | |
| 14 | +#include <hamsandwich> | |
| 15 | + | |
| 16 | +public plugin_init() | |
| 17 | +{ | |
| 18 | + register_plugin("Ham Demo", "1.0", "CSB") | |
| 19 | + | |
| 20 | + RegisterHam(Ham_TakeDamage, "player", "fw_take_damage_pre", 0) | |
| 21 | + RegisterHam(Ham_Spawn, "player", "fw_spawn_post", 1) | |
| 22 | + RegisterHam(Ham_Killed, "player", "fw_killed_pre", 0) | |
| 23 | +}</pre> | |
| 24 | +<p>The classname <code>"player"</code> hooks the method on every player entity. You can hook other entities the same way — <code>"weapon_ak47"</code>, <code>"func_breakable"</code> — as long as the function exists on that class.</p> | |
| 25 | + | |
| 26 | +<h2>2. Ham_TakeDamage: reading the parameters</h2> | |
| 27 | +<p>The <code>TakeDamage</code> forward hands you the victim, the inflictor entity, the attacker, the damage amount, and the damage-type bits:</p> | |
| 28 | +<pre>public fw_take_damage_pre(victim, inflictor, attacker, Float:damage, damagebits) | |
| 29 | +{ | |
| 30 | + // ignore world/self damage | |
| 31 | + if (!is_user_connected(attacker) || victim == attacker) | |
| 32 | + return HAM_IGNORED | |
| 33 | + | |
| 34 | + new vname[32], aname[32] | |
| 35 | + get_user_name(victim, vname, charsmax(vname)) | |
| 36 | + get_user_name(attacker, aname, charsmax(aname)) | |
| 37 | + | |
| 38 | + server_print("%s is hitting %s for %.0f", aname, vname, damage) | |
| 39 | + return HAM_IGNORED | |
| 40 | +}</pre> | |
| 41 | +<p><code>damagebits</code> is a bitfield of <code>DMG_*</code> flags (bullet, blast, fall, etc.), useful for distinguishing a knife from a grenade from fall damage.</p> | |
| 42 | + | |
| 43 | +<h2>3. Modifying and blocking</h2> | |
| 44 | +<p>To change a parameter, write it back with the <code>SetHamParam*</code> natives — the argument number matches the parameter position. Damage is parameter 4:</p> | |
| 45 | +<pre>public fw_take_damage_pre(victim, inflictor, attacker, Float:damage, damagebits) | |
| 46 | +{ | |
| 47 | + if (!is_user_connected(attacker)) | |
| 48 | + return HAM_IGNORED | |
| 49 | + | |
| 50 | + // double all player damage | |
| 51 | + SetHamParamFloat(4, damage * 2.0) | |
| 52 | + return HAM_HANDLED | |
| 53 | +}</pre> | |
| 54 | +<p>To cancel the call outright — total damage immunity, spawn protection — return <code>HAM_SUPERCEDE</code>. That prevents the original function from running at all:</p> | |
| 55 | +<pre>public fw_take_damage_pre(victim, inflictor, attacker, Float:damage, damagebits) | |
| 56 | +{ | |
| 57 | + if (is_spawn_protected(victim)) | |
| 58 | + return HAM_SUPERCEDE // take no damage | |
| 59 | + | |
| 60 | + return HAM_IGNORED | |
| 61 | +}</pre> | |
| 62 | +<p>The return values: <code>HAM_IGNORED</code> (do nothing special), <code>HAM_HANDLED</code> (you changed a param, keep going), <code>HAM_SUPERCEDE</code> (block the original), and <code>HAM_OVERRIDE</code> (block and substitute your own return value, set with <code>SetHamReturn*</code>).</p> | |
| 63 | + | |
| 64 | +<h2>4. Pre versus post</h2> | |
| 65 | +<p>A <strong>pre</strong> hook (flag <code>0</code>) runs before the original function — the only place you can modify parameters or supersede the call. A <strong>post</strong> hook (flag <code>1</code>) runs after, when the effect has already happened. Use post for <code>Ham_Spawn</code> when you want to hand out weapons or set health <em>after</em> the game has reset the player:</p> | |
| 66 | +<pre>public fw_spawn_post(id) | |
| 67 | +{ | |
| 68 | + if (!is_user_alive(id)) | |
| 69 | + return HAM_IGNORED | |
| 70 | + | |
| 71 | + set_pev(id, pev_health, 150.0) // give bonus HP on spawn | |
| 72 | + return HAM_IGNORED | |
| 73 | +}</pre> | |
| 74 | +<p>Setting health in a <em>pre</em> spawn hook would be pointless — the game overwrites it during the spawn. Match the hook direction to when the state you care about actually exists.</p> | |
| 75 | + | |
| 76 | +<h2>5. Calling the function yourself</h2> | |
| 77 | +<p><code>ExecuteHamB</code> lets you invoke a Ham function manually — for example, to kill a player through the real code path so death handling runs correctly:</p> | |
| 78 | +<pre>// deal 1000 damage to slay a player properly | |
| 79 | +ExecuteHamB(Ham_TakeDamage, id, 0, id, 1000.0, DMG_GENERIC)</pre> | |
| 80 | + | |
| 81 | +<h2>Common errors</h2> | |
| 82 | +<ul> | |
| 83 | +<li><strong>Server crashes on load</strong> — wrong classname, or hooking a function the class does not have. <code>Ham_TakeDamage</code> on <code>"player"</code> is safe; a typo in the classname is not.</li> | |
| 84 | +<li><strong>Damage change ignored</strong> — you set the param in a <em>post</em> hook. Parameters can only be modified in a pre hook, before the original runs.</li> | |
| 85 | +<li><strong>Superceding TakeDamage breaks other plugins</strong> — if another plugin also hooks it and expects the call to proceed, your <code>HAM_SUPERCEDE</code> hides the event from it. Prefer setting damage to <code>0.0</code> and returning <code>HAM_HANDLED</code> when you only mean to null the damage, not the whole call.</li> | |
| 86 | +<li><strong><code>run time error</code> after the hook</strong> — you used an invalid entity index (attacker <code>0</code> is the world). Guard with <code>is_user_connected</code> before touching player data. See <a href="/tutorials/fix-amxx-run-time-error-4">run-time error 4</a>.</li> | |
| 87 | +</ul> | |
| 88 | + | |
| 89 | +<h2>Verification</h2> | |
| 90 | +<p>Load the plugin, take a hit in-game, and confirm the <code>server_print</code> reports the expected damage. For a modifier, check that the scoreboard/health reflects the change — double damage should drop a full-HP player in half the usual hits. For <code>HAM_SUPERCEDE</code> immunity, stand in fire and confirm health never drops. When you need named members and a cleaner API on a ReGameDLL server, <a href="/tutorials/reapi-getting-started">ReAPI</a> hooks the same functions with readable arguments; for lighter needs, <a href="/tutorials/register-event-vs-ham-vs-reapi">compare all three hook types</a>.</p> | |
| 0 | 91 |









