Players and entities
Everything in the game world is an entity: players, weapons, grenades, doors, the bomb. In a plugin an entity is an object, and its data are properties you read and assign:
server.addCommand("/low", ({ player }) => lowGravity(player));
function lowGravity(player: Player) {
player.gravity = 0.5;
player.renderMode = "additive";
console.log(player.classname); // "player"
const here = player.origin; // a Vector: here.x, here.distanceTo(...)
const nade = Entity.find({ classname: "grenade", owner: player });
if (nade) nade.velocity = [0.0, 0.0, 300.0];
}
There are three classes:
Entity— any entity;Player— a player: everything anEntityhas, plus the player's own data and actions (Players);Weapon— a weapon: everything anEntityhas, plus its clip, its owner and its timings.
A player comes from the event or the command that is about him
(event.player) or from server.players; an entity from an event, from
Entity.find or Entity.create. new Player(id) and new Entity(id) wrap an
index you already have.
Properties
Entity has every field the engine keeps for an entity, Player every
member of the game's player class, and Weapon every member of a weapon. A
property has the type its data has:
| Data | Property | Type |
|---|---|---|
| a number | gravity, money, maxSpeed | number |
| a position or a direction | origin, velocity, angles | Vector to read, any three numbers to write |
| a text | classname, model | string |
| yes or no | nightVisionOn | boolean |
| one of the engine's values | renderMode, moveType, kevlar | a name, see below |
| a set of flags | flags, hideHud, buttons | an array of names, see Flags |
| another entity | activeItem, weapon.player | Weapon | null, Player | null |
The editor lists them all. Hover over a property and the tooltip says what it
is - "The player's money" - with its unit, range and usual values, and the
last line names it as Pawn does (Pawn: `CBasePlayer::m_iAccount` ), for
when you know a field by its Pawn name.
The names are the Pawn names in camelCase, without the prefix -
var_rendermode is renderMode, m_iHideHUD is hideHud - or a player's
word where the game's is its own: m_iAccount is money,
m_flVelocityModifier is slowdown.
A number written to a whole-number field loses its fraction:
player.money = 99.9 sets 99.
What the player sees since v0.2
The game sends a player his health, armour, field of view and hidden HUD parts by itself when they change. A few fields his game learns only from a message, and writing one sends it to him, as the game does:
| Property | What he sees at once |
|---|---|
money | the new amount on his HUD, flashing |
kevlar | the helmet on his HUD's armour, or none |
flashlightBattery | the flashlight's charge |
hasNightVision, nightVisionOn | the goggles in his buy menu; his screen in night vision or back |
hasDefuser | the kit on his model, its icon on his HUD, the kit in his buy menu |
player.money += 500;
The message goes out as the game's own do, so a listener of the
money message hears it - and without ReAPI, so does addMoney, which hears money through
that message.
Free fields
iuser1–iuser4, fuser1–fuser4, vuser1–vuser4 and
euser1–euser4 are spare fields a plugin may use for its own values on any
entity that is not a player. On a player the game uses some of them:
iuser1–iuser3 for spectating, iuser4 is rewritten every frame, fuser2
and fuser3 are movement. The tooltip of each says which. What a plugin
remembers about a player belongs in its own object per player, or in a
shared player field.
Names instead of numbers
A field that holds one of the engine's fixed values takes and gives a name:
function hide(player: Player, box: Entity) {
box.renderMode = "additive";
box.moveType = "noclip";
box.solid = "trigger";
player.kevlar = "vestHelmet";
if (player.waterLevel == "head") console.log(`${player.name} is under water`);
if (player.deadFlag != "alive") return;
if (player.observerMode == "inEye") console.log("watching through someone's eyes");
}
| Property | On | Type: names |
|---|---|---|
renderMode | Entity | RenderMode: "normal", "color", "texture", "glow", "alpha", "additive" |
renderFx | Entity | RenderFx: "none", "glowShell", "pulseSlow", "hologram", … |
moveType | Entity | MoveType: "none", "walk", "fly", "toss", "noclip", "bounce", "follow", … |
solid | Entity | Solid: "none", "trigger", "box", "slideBox", "bsp" |
takeDamage | Entity | TakeDamage: "no", "yes", "aim" |
deadFlag | Entity | DeadFlag: "alive", "dying", "dead", "respawnable", "discardBody" |
waterLevel | Entity | WaterLevel: "none", "feet", "waist", "head" |
waterType | Entity | Contents: "empty", "water", "slime", "lava", … |
fixAngle | Entity | FixAngle: "none", "set", "addYaw" |
observerMode, observerLastMode | Player | ObserverMode: "none", "chaseLocked", "chaseFree", "roaming", "inEye", "mapFree", "mapChase" |
kevlar | Player | ArmorType: "none", "vest", "vestHelmet" |
lastHitGroup | Player | HitGroup: "generic", "head", "chest", "stomach", "leftArm", … "shield" |
joiningState | Player | JoinState: "joined", "showMotd", "pickingTeam", … |
openMenu | Player | GameMenu: "none", "team", "buy", "buyRifle", "radio1", … |
modelName | Player | PlayerModel: "urban", "gign", "terror", "leet", … "unassigned", "auto" |
ignoreGlobalChat | Player | IgnoredChat: "none", "enemy", "all" |
throwDirection | Player | ThrowDirection: "none", "forward", "backward", "grenade", … |
bloodColor | Player | BloodColor: "none", "red", "yellow" |
musicState | Player | MusicState: "silent", "calm", "intense" |
The editor suggests the names and rejects a typo; the tooltip says what each
one means. The types are exported from @amxts/core, for a parameter of your
own: function glow(player: Player, fx: RenderFx).
observerMode is the player's iuser1 read by name; iuser1 itself stays a
number. Assigning a mode switches his camera as the game does when he picks
it himself: player.observerMode = "inEye" finds him someone to watch -
observerTarget, if he may still watch that one - or switches to
"roaming" when there is nobody; the mode he asked for becomes
observerLastMode, and the mode's name shows on his screen. Assigning the
mode he is in changes nothing. "none" only clears the field: the game ends
spectating when he spawns.
A value no name stands for - another plugin or a mod wrote it - reads as
"unknown", and assigning "unknown" leaves the field as it is. To see the
number itself, read the field with the native:
get_entvar(player.id, var_rendermode).Vectors — Vector
function launch(player: Player, target: Player) {
const here = player.origin; // a Vector
const distance = here.distanceTo(target.origin); // in units
const up = new Vector(0.0, 0.0, 1.0);
player.velocity = here.subtract(target.origin).normalize().scale(400).add(up.scale(200));
console.log(`${here.x} ${here[0]} ${distance}`); // x and [0] are the same number
}
A Vector is three numbers - x, y, z - with the usual operations:
| Method | Gives |
|---|---|
add(v), subtract(v) | a new vector |
scale(n) | a new vector, n times as long |
dot(v) | a number |
distanceTo(v) | the distance to v |
magnitude() | the vector's length |
normalize() | a new vector one unit long; a zero vector stays zero |
add, subtract, dot and distanceTo take any three numbers, a literal
included: here.add([0, 0, 64]). A property that takes a vector takes a
literal too: player.velocity = [0, 0, 300].
A Vector is an array of three numbers, so v[0] reads x and a native
that takes Float:v[3] takes a Vector. For the same reason its length is
magnitude(): v.length is the array's, and it is 3.
Each read of a vector property - player.origin, player.velocity - is a
new Vector, and making one takes time. In code that runs
every frame, keep a Vector and have the property written into it with its
get method, which returns it:
const origin = new Vector(); // once
const velocity = new Vector();
function onFrame(player: Player) {
player.getOrigin(origin); // the same numbers as player.origin, no new Vector
player.getVelocity(velocity);
}
Finding, creating, removing
function cleanUp(player: Player) {
const targets = Entity.findAll({ classname: "info_target" });
const near = Entity.findAll({ near: player.origin, radius: 200 });
const mine = Entity.findAll({ classname: "grenade", owner: player });
const bomb = Entity.find({ model: "models/w_c4.mdl" }); // Entity | null
const box = Entity.create("info_target"); // Entity | null
if (box) box.origin = player.origin;
for (const target of targets) target.remove();
console.log(`${near.length} near, ${mine.length} grenades, bomb: ${bomb != null}`);
}
Entity.findAll(filter) returns every entity that matches, Entity.find
the first one or null. Every filter field is optional, and an entity has to
match all the fields given:
| Field | Matches |
|---|---|
classname | the class name: "grenade", "weaponbox", "func_door" |
model | the model: "models/w_c4.mdl" |
owner | whose it is: a grenade's thrower, a weapon's carrier |
near, radius | the entities within radius units of the point near |
With no filter, findAll() is every entity on the map.
Entity.create(classname) makes an entity, or returns null when the engine
cannot. entity.remove() removes it at the end of the current frame, the way
the game removes its own entities, so it is safe from inside the entity's own
touch or think. Until the frame ends findAll still finds it.
const boxes: Entity[] = [];
function clearBoxes() {
for (const box of boxes) {
if (box.exists) box.remove(); // skips one the game has removed since
}
}
entity.exists is true while the entity is in the world: false once the
engine has freed it, for a player who has left, and for 0, which is no
entity. An entity kept for later - in an array, in a timer - may be gone by
then; exists is how to ask.
const box = Entity.create("info_target");
if (box) {
box.model = "models/myplugin/box.mdl"; // precached with server.precache
box.setSize([-16, -16, 0], [16, 16, 32]);
}
Assigning entity.model sets the model as the game does: modelIndex and
the size follow. The model has to be precached
(sounds and precaching).
entity.setSize(mins, maxs) sets the box the entity collides by, its corners
relative to origin; a box whose mins is above its maxs on any axis is
refused, with an error in the console. Writing mins or maxs changes the
numbers and nothing else.
Weapons
server.addCommand("/unload", ({ player }) => unloadAll(player));
function unloadAll(player: Player) {
const weapon = player.activeItem; // Weapon | null: the weapon in his hands
if (weapon && weapon.kind == "knife") return;
for (const item of player.items) unload(item);
}
function unload(weapon: Weapon) {
weapon.clip = 0;
const holder = weapon.player; // Player | null: who carries it
if (holder) console.log(`${holder.name}: ${weapon.kind} unloaded`);
}
weapon.kindsays which weapon it is, as a name:"knife","ak47","awp","hegrenade","flashbang", … (the typeWeaponKind; an unknown weapon is"none").weapon.kindIdis the same as the game's number.weapon.classnameis the weapon's name as the player's methods take it, aWeaponNamesuch as"weapon_ak47":player.give(weapon.classname),player.getAmmo(weapon.classname).player.activeItem,player.lastItemandplayer.activeItemSentare aWeaponornull;weapon.playeris thePlayerwho carries it, ornullwhen it lies on the ground;weapon.nextis the next weapon in the same inventory slot. These are read-only.player.itemsis every weapon the player carries, grenades included; one of them isfind:player.items.find(item => item.classname == "weapon_ak47"),nullif he has none.weapon.owneris the engine's owner field, an entity index;weapon.playeris the one to use for the carrier.- The weapon's own members drop their Pawn prefix:
m_Weapon_iClipisclip,m_Weapon_flNextPrimaryAttackisnextPrimaryAttack.
The player's basics
name, health, armor, frags, deaths, team, ip, steamId,
isAlive, isConnected and isBot work on every server, ReAPI or not.
A player's health is a whole number, and 0 or less kills him. Every
other entity has health too, a number that may be a fraction:
box.health = 50 - a breakable breaks when damage takes it to 0.
player.fov is the player's field of view in degrees, the one the game
zooms by: 90 is normal, 40 and 10 through a sniper scope, and
player.fov = 110 shows him more. The game sets it back at spawn, when he
draws a weapon and when he zooms, so a plugin that keeps a wider view sets it
again then.
The rest of what a player can do - teams, actions, the HUD - is on Players.
Fields that are not properties
Some of the game's data has no property. Such members are read and written
with the ReAPI natives get_member and set_member, each as what it holds
(calling natives), or,
on a server without ReAPI, with fakemeta's get_ent_data and
set_ent_data, which name the class and the member:
get_ent_data(player.id, "CBasePlayer", "m_rgAmmo", 5):
- array members, such as the ammo per type
m_rgAmmo, with the element's index after the member (the weapons themselves areplayer.items); var_controllerandvar_blending, withget_entvarandset_entvar;- members the game keeps for something other than a Counter-Strike player:
the monster AI's (
m_Activity,m_IdealActivity,m_MonsterState,m_IdealMonsterState,m_afConditions,m_afMemory,m_vecEnemyLKP,m_HackedGunPos,m_hTargetEnt), Half-Life's team namem_szTeamName(the side isplayer.team) and armour typevar_armortype(the armour isplayer.armorandplayer.kevlar), Condition Zero's career mode (m_bInCareerGame,m_fCareerRoundMenuTime,m_iCareerMatchWins,m_fCareerMatchMenuTime,m_iRoundWinDifference), the voice messages' numbers (m_msgPlayerVoiceMask,m_msgRequestState), a weapon's firing events (m_Weapon_usFireGlock18,m_Weapon_usFireFamas) and a pistol's last shotm_Weapon_flLastFire(every weapon's isweapon.lastFireTime).
A text member is a property like any other: player.animExtension is the
animation set the player's model holds his weapon with, e.g. "knife"
(m_szAnimExtention).
autoSwitchWeapon and shotgunReloadStage are plain numbers, the values
Pawn uses.
Performance
A plugin is compiled to machine code before the server loads it, so the work it does on its own - loops, arithmetic, arrays, text - is fast. What costs time is crossing between the plugin and the game - a call into AMX Mod X, an event on its way to a listener - and memory a busy function asks for again and again. This page says where the time goes and gives the habits that follow.
Players: actions
A Player is a player on the server. The event or the command that is about him gives him to you (event.player, player in a command's handler), and server.players lists everyone. His game data - gravity, origin, hideHud and the rest - are properties, described on Players and entities. This page is about what you do with a player: find him, move him to a team, give him weapons, show him text, and share his state with other plugins.