.agents/skills/amxx-modding-kit/api/custom-weapons/SKILL.md
Guide for implementing custom weapons using OOP-style patterns in AMX Mod X. Use when creating new weapons or extending existing ones with the Custom Weapons API.
npx skillsauth add hedgefog/amxx-modding-kit amxx-modding-kit-api-custom-weaponsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
OOP-style weapon system for creating and extending weapons with custom behavior.
For complete API documentation, see README.md.
Use m_ prefix with Hungarian notation type prefix:
// Format: m_{Type}{Name}
new const m_flChargeTime[] = "flChargeTime"; // float - charge start time
new const m_bSilenced[] = "bSilenced"; // bool - silencer attached
new const m_iFireMode[] = "iFireMode"; // int - current fire mode
new const m_pTarget[] = "pTarget"; // pointer - locked target
Use PascalCase name only (no prefix):
new const SetPower[] = "SetPower";
new const ToggleSilencer[] = "ToggleSilencer";
new const StartCharge[] = "StartCharge";
public plugin_precache() {
// Precache resources BEFORE registration
precache_model(g_szModelV);
precache_model(g_szModelP);
precache_model(g_szModelW);
precache_sound(g_szShotSound);
// Register new weapon class
CW_RegisterClass(WEAPON_NAME);
// Or extend existing: CW_RegisterClass(WEAPON_NAME, "weapon_othercustomweapon");
// Implement base methods (override virtual methods)
CW_ImplementClassMethod(WEAPON_NAME, CW_Method_Create, "@Weapon_Create");
CW_ImplementClassMethod(WEAPON_NAME, CW_Method_Deploy, "@Weapon_Deploy");
CW_ImplementClassMethod(WEAPON_NAME, CW_Method_PrimaryAttack, "@Weapon_PrimaryAttack");
CW_ImplementClassMethod(WEAPON_NAME, CW_Method_Reload, "@Weapon_Reload");
// Register custom methods
CW_RegisterClassMethod(WEAPON_NAME, SetPower, "@Weapon_SetPower", CW_Type_Cell);
}
Custom weapons can only be inherited from other custom weapons.
static szModel[64]; CW_GetMemberString(this, CW_Member_szModel, szModel, charsmax(szModel));
static Float:vecSpread[3]; CW_GetMemberVec(this, m_vecSpread, vecSpread);
new Float:flChargeTime = CW_GetMember(this, m_flChargeTime);
new iFireMode = CW_GetMember(this, m_iFireMode);
CW_SetMemberString(this, CW_Member_szModel, g_szModelW);
CW_SetMemberVec(this, m_vecSpread, Float:{0.02, 0.02, 0.0});
CW_SetMember(this, m_flChargeTime, 0.0);
CW_SetMember(this, m_iFireMode, 1);
When you need to get float member value in conditions or for other inline operations you should explicitly provide Float: tag to make sure compiler will use correct type.
if (Float:CW_GetMember(this, m_flNextUpdate) <= get_gametime()) {
// Do something
}
Set weapon properties. Never modify engine data here.
@Weapon_Create(const this) {
CW_CallBaseMethod();
// Built-in members
CW_SetMemberString(this, CW_Member_szModel, g_szModelW);
CW_SetMember(this, CW_Member_iId, WEAPON_ID);
CW_SetMember(this, CW_Member_iMaxClip, 30);
CW_SetMember(this, CW_Member_iSlot, WEAPON_SLOT);
CW_SetMember(this, CW_Member_iPosition, WEAPON_POSITION);
// Custom members
CW_SetMember(this, m_flChargeTime, 0.0);
CW_SetMember(this, m_bSilenced, false);
// ❌ WRONG: Don't use engine functions here
// set_pev(this, pev_dmg, 50.0);
}
Handle weapon draw:
@Weapon_Deploy(const this) {
CW_CallBaseMethod();
// Use native method for default deploy behavior
CW_CallNativeMethod(this, CW_Method_DefaultDeploy, g_szModelV, g_szModelP, ANIM_DRAW, "rifle");
}
Handle shooting:
@Weapon_PrimaryAttack(const this) {
CW_CallBaseMethod();
static Float:vecSpread[3]; vecSpread = Float:{0.02, 0.02, 0.0};
// DefaultShot returns true if shot was fired
if (CW_CallNativeMethod(this, CW_Method_DefaultShot, 25.0, 1.0, 0.1, vecSpread, 1)) {
static pPlayer; pPlayer = get_ent_data_entity(this, "CBasePlayerItem", "m_pPlayer");
emit_sound(pPlayer, CHAN_WEAPON, g_szShotSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
}
}
Handle reloading:
@Weapon_Reload(const this) {
CW_CallBaseMethod();
if (CW_CallNativeMethod(this, CW_Method_DefaultReload, ANIM_RELOAD, 2.5)) {
static pPlayer; pPlayer = get_ent_data_entity(this, "CBasePlayerItem", "m_pPlayer");
emit_sound(pPlayer, CHAN_WEAPON, g_szReloadSound, VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
}
}
Recurring logic with static variables for performance:
@Weapon_Think(const this) {
static pPlayer; pPlayer = get_ent_data_entity(this, "CBasePlayerItem", "m_pPlayer");
static Float:flChargeTime; flChargeTime = CW_GetMember(this, m_flChargeTime);
// Weapon logic...
CW_CallBaseMethod();
}
// Give weapon to player
new pWeapon = CW_Give(pPlayer, WEAPON_NAME);
if (pWeapon != FM_NULLENT) {
CW_SetMember(pWeapon, m_flChargeTime, 0.0);
}
// Create weapon entity (for world placement)
new pWeapon = CW_Create(WEAPON_NAME);
if (pWeapon != FM_NULLENT) {
engfunc(EngFunc_SetOrigin, pWeapon, vecOrigin);
dllfunc(DLLFunc_Spawn, pWeapon);
}
if (CW_PlayerHasWeapon(pPlayer, WEAPON_NAME)) {
// Player has this weapon
}
// Get weapon entity from player inventory
new pWeapon = CW_PlayerFindWeapon(pPlayer, WEAPON_NAME);
if (pWeapon != FM_NULLENT) {
// Found weapon entity
}
// Call custom method
CW_CallMethod(pWeapon, SetPower, 100.0);
CW_CallMethod(pWeapon, StartCharge);
// Call native method with parameters
CW_CallNativeMethod(pWeapon, CW_Method_DefaultDeploy, g_szModelV, g_szModelP, ANIM_DRAW, "rifle");
Always call CW_CallBaseMethod() to invoke parent implementation:
@Weapon_PrimaryAttack(const this) {
if (!CW_CallMethod(this, CanFire)) return;
CW_CallBaseMethod();
// Additional logic after parent method
PlayMuzzleFlash(this);
}
Important: There is no CW_GetPlayer native. Use engine data access:
new pPlayer = get_ent_data_entity(this, "CBasePlayerItem", "m_pPlayer");
Listen to weapon events from other plugins:
public plugin_init() {
CW_RegisterClassNativeMethodHook(WEAPON_NAME, CW_Method_PrimaryAttack, "CWHook_Rifle_PrimaryAttack");
}
public CWHook_Rifle_PrimaryAttack(const this) {
// React to weapon fire...
return CW_HANDLED;
}
Hook callback naming: CWHook_{WeaponName}_{Method}
public CWHook_Rifle_PrimaryAttack(const this)
public CWHook_Shotgun_Reload(const this)
public plugin_precache() {
CW_Ammo_Register("rifle_ammo", CSW_AK47, 90); // name, engine type, max amount
}
CW_GiveAmmo(pPlayer, "rifle_ammo", 30);
CW_RegisterClassCreate for member initialization onlyDeploy for setting view/player modelsFM_NULLENT after CW_Create or CW_GiveCW_CallNativeMethod for built-in behaviorsget_ent_data_entity(this, "CBasePlayerItem", "m_pPlayer")m_ prefix and Hungarian notationtools
Best practices for organizing large AMX Mod X projects with multiple plugins sharing entities, weapons, events, and other definitions. Use when creating mod-scale projects that need consistent namespace patterns and shared constants.
development
Guide for Waypoint Markers API creating 3D waypoint sprites visible through walls with per-player visibility.
development
Guide for States API usage implementing state machines with transitions, guards, and lifecycle hooks.
development
Guide for Shops API usage creating in-game shops with items, custom balance systems, and access control.