States API
SkillDev toolsGuide for States API usage implementing state machines with transitions, guards, and lifecycle hooks.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the States API skill
What this skill tells your AI
The instructions your AI receives, as published by hedgefog/amxx-modding-kit in .agents/skills/amxx-modding-kit/api/states/SKILL.md and read by ahel’s review.
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.
State Context Registration
Define States
enum HealthState {
HealthState_Healthy = 0,
HealthState_Injured,
HealthState_Critical,
HealthState_Dead
};
Register Context
#define STATE_CONTEXT_HEALTH "myplugin.health"
public plugin_precache() {
// Register context with initial state
State_Context_Register(STATE_CONTEXT_HEALTH, HealthState_Healthy);
}
Register Hooks
Enter/Exit Hooks
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");
}
Transition Hook
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!");
}
Change Hook
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);
}
Change Guard
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;
}
State Manager Lifecycle
Create Manager
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);
}
Destroy Manager
public client_disconnected(pPlayer) {
if (g_rgpPlayerStateManagers[pPlayer] != StateManager_Invalid) {
State_Manager_Destroy(g_rgpPlayerStateManagers[pPlayer]);
g_rgpPlayerStateManagers[pPlayer] = StateManager_Invalid;
}
}
State Transitions
Immediate Transition
// Set state immediately
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Injured);
Timed Transition
// Transition to state after delay
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Critical, 5.0);
Force Transition
// Force transition even during another transition
State_Manager_SetState(g_rgpPlayerStateManagers[pPlayer], HealthState_Dead, _, true);
Reset to Initial State
// Reset to initial state (HealthState_Healthy)
State_Manager_ResetState(g_rgpPlayerStateManagers[pPlayer]);
Query Current State
new HealthState:iState = State_Manager_GetState(g_rgpPlayerStateManagers[pPlayer]);
switch (iState) {
case HealthState_Healthy: { /* ... */ }
case HealthState_Injured: { /* ... */ }
case HealthState_Critical: { /* ... */ }
case HealthState_Dead: { /* ... */ }
}
Hook Implementations
Enter Hook
@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);
}
Exit Hook
@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...");
}
Common Patterns
Health-Based State Updates
@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);
}
}
Infection State Machine
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);
}
Reset on Spawn
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;
}
Checklist
- Define state enum
- Register context in
plugin_precache - Create managers in
client_connect - Destroy managers in
client_disconnected - Register enter/exit hooks for state effects
- Use guard hooks to validate transitions
- Get user token with
State_Manager_GetUserToken - Reset states on spawn if appropriate
Signals
- GitHub stars
- 32
- Forks
- 4
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
amxx-modding-kit-api-states- Source
- github.com/hedgefog/amxx-modding-kit