.agents/skills/amxx-modding-kit/api/states/SKILL.md
Guide for States API usage implementing state machines with transitions, guards, and lifecycle hooks.
npx skillsauth add hedgefog/amxx-modding-kit amxx-modding-kit-api-statesInstall 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.
State machine implementation for managing entity and player states with transitions, guards, enter/exit hooks, and timed state changes.
For complete API documentation, see README.md.
enum HealthState {
HealthState_Healthy = 0,
HealthState_Injured,
HealthState_Critical,
HealthState_Dead
};
#define STATE_CONTEXT_HEALTH "myplugin.health"
public plugin_precache() {
// Register context with initial state
State_Context_Register(STATE_CONTEXT_HEALTH, HealthState_Healthy);
}
public plugin_precache() {
State_Context_Register(STATE_CONTEXT_HEALTH, HealthState_Healthy);
// Enter hooks - called when entering state
State_Context_RegisterEnterHook(STATE_CONTEXT_HEALTH, HealthState_Healthy, "@Health_Healthy_Enter");
State_Context_RegisterEnterHook(STATE_CONTEXT_HEALTH, HealthState_Injured, "@Health_Injured_Enter");
State_Context_RegisterEnterHook(STATE_CONTEXT_HEALTH, HealthState_Critical, "@Health_Critical_Enter");
// Exit hooks - called when leaving state
State_Context_RegisterExitHook(STATE_CONTEXT_HEALTH, HealthState_Critical, "@Health_Critical_Exit");
}
Called on specific state-to-state transition:
public plugin_precache() {
// ...
// Called when transitioning from Injured to Critical
State_Context_RegisterTransitionHook(
STATE_CONTEXT_HEALTH,
HealthState_Injured,
HealthState_Critical,
"@Health_Injured_To_Critical"
);
}
@Health_Injured_To_Critical(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
client_print(pPlayer, print_center, "Warning: Condition worsening!");
}
Called on any state change:
public plugin_precache() {
State_Context_RegisterChangeHook(STATE_CONTEXT_HEALTH, "@Health_Changed");
}
@Health_Changed(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
static HealthState:iNewState; iNewState = State_Manager_GetState(this);
// Update HUD, play sound, etc.
UpdateHealthHUD(pPlayer, iNewState);
}
Validate state changes before they happen:
public plugin_precache() {
State_Context_RegisterChangeGuard(STATE_CONTEXT_HEALTH, "@Health_CanChange");
}
bool:@Health_CanChange(const StateManager:this, any:iOldState, any:iNewState) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
// Can't become healthy if poisoned
if (iNewState == HealthState_Healthy && IsPlayerPoisoned(pPlayer)) {
return false;
}
return true;
}
new StateManager:g_rgpPlayerStateManagers[MAX_PLAYERS + 1] = { StateManager_Invalid, ... };
public client_connect(pPlayer) {
// Create manager with player as user token
g_rgpPlayerStateManagers[pPlayer] = State_Manager_Create(STATE_CONTEXT_HEALTH, pPlayer);
}
public client_disconnected(pPlayer) {
if (g_rgpPlayerStateManagers[pPlayer] != StateManager_Invalid) {
State_Manager_Destroy(g_rgpPlayerStateManagers[pPlayer]);
g_rgpPlayerStateManagers[pPlayer] = StateManager_Invalid;
}
}
// Set state immediately
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Injured);
// Transition to state after delay
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Critical, 5.0);
// Force transition even during another transition
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Dead, _, true);
// Reset to initial state (HealthState_Healthy)
State_Manager_ResetState(g_rgpPlayerStateManagers[pPlayer]);
new HealthState:iState = State_Manager_GetState(g_rgpPlayerStateManagers[pPlayer]);
switch (iState) {
case HealthState_Healthy: { /* ... */ }
case HealthState_Injured: { /* ... */ }
case HealthState_Critical: { /* ... */ }
case HealthState_Dead: { /* ... */ }
}
@Health_Healthy_Enter(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
client_print(pPlayer, print_center, "You are healthy!");
// Clear any negative effects
ClearHealthEffects(pPlayer);
}
@Health_Injured_Enter(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
client_print(pPlayer, print_center, "You are injured. Be careful!");
// Apply visual effect
ApplyInjuredEffect(pPlayer);
}
@Health_Critical_Enter(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
client_print(pPlayer, print_center, "CRITICAL! Find a medkit!");
// Apply urgent effects
ApplyCriticalEffect(pPlayer);
StartHeartbeatSound(pPlayer);
}
@Health_Critical_Exit(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
// Remove critical effects
StopHeartbeatSound(pPlayer);
ClearCriticalEffect(pPlayer);
client_print(pPlayer, print_center, "Condition stabilizing...");
}
@Player_TakeDamage_Post(const pPlayer) {
UpdateHealthState(pPlayer);
}
@Player_Healed(const pPlayer) {
UpdateHealthState(pPlayer);
}
UpdateHealthState(const pPlayer) {
static StateManager:pManager; pManager = g_rgpPlayerStateManagers[pPlayer];
if (pManager == StateManager_Invalid) return;
static Float:flHealth; pev(pPlayer, pev_health, flHealth);
if (flHealth <= 0.0) {
State_Manager_SetState(pManager, HealthState_Dead);
} else if (flHealth < 20.0) {
State_Manager_SetState(pManager, HealthState_Critical);
} else if (flHealth < 50.0) {
State_Manager_SetState(pManager, HealthState_Injured);
} else {
State_Manager_SetState(pManager, HealthState_Healthy);
}
}
enum InfectionState {
InfectionState_None = 0,
InfectionState_Infected,
InfectionState_Transforming,
InfectionState_Zombie
};
#define STATE_CONTEXT_INFECTION "myplugin.infection"
public plugin_precache() {
State_Context_Register(STATE_CONTEXT_INFECTION, InfectionState_None);
State_Context_RegisterEnterHook(STATE_CONTEXT_INFECTION, InfectionState_Infected, "@Infection_Infected_Enter");
State_Context_RegisterEnterHook(STATE_CONTEXT_INFECTION, InfectionState_Transforming, "@Infection_Transforming_Enter");
State_Context_RegisterEnterHook(STATE_CONTEXT_INFECTION, InfectionState_Zombie, "@Infection_Zombie_Enter");
State_Context_RegisterChangeGuard(STATE_CONTEXT_INFECTION, "@Infection_CanChange");
}
// When player is bitten
InfectPlayer(const pPlayer) {
new StateManager:pManager = g_rgpPlayerInfectionState[pPlayer];
// Start infection, transform after 10 seconds
State_Manager_SetState(pManager, InfectionState_Infected);
State_Manager_SetState(pManager, InfectionState_Transforming, 10.0);
}
@Infection_Transforming_Enter(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
// Schedule final transformation
State_Manager_SetState(this, InfectionState_Zombie, 5.0);
client_print(pPlayer, print_center, "Transformation beginning...");
}
@Infection_Zombie_Enter(const StateManager:this) {
static pPlayer; pPlayer = State_Manager_GetUserToken(this);
// Complete transformation
PlayerRole_Player_AssignRole(pPlayer, ROLE_ZOMBIE);
}
public HamHook_Player_Spawn_Post(const pPlayer) {
if (!is_user_alive(pPlayer)) return HAM_IGNORED;
// Reset health state
State_Manager_ResetState(g_rgpPlayerStateManagers[pPlayer]);
// Reset infection state
State_Manager_ResetState(g_rgpPlayerInfectionState[pPlayer]);
return HAM_HANDLED;
}
plugin_precacheclient_connectclient_disconnectedState_Manager_GetUserTokentools
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 Shops API usage creating in-game shops with items, custom balance systems, and access control.
development
Guide for Rounds API usage managing round-based gameplay including timing, win conditions, and round events.