Game

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 an Entity has, plus the player's own data and actions (Players);
  • Weapon — a weapon: everything an Entity has, 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:

DataPropertyType
a numbergravity, money, maxSpeednumber
a position or a directionorigin, velocity, anglesVector to read, any three numbers to write
a textclassname, modelstring
yes or nonightVisionOnboolean
one of the engine's valuesrenderMode, moveType, kevlara name, see below
a set of flagsflags, hideHud, buttonsan array of names, see Flags
another entityactiveItem, weapon.playerWeapon | 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:

PropertyWhat he sees at once
moneythe new amount on his HUD, flashing
kevlarthe helmet on his HUD's armour, or none
flashlightBatterythe flashlight's charge
hasNightVision, nightVisionOnthe goggles in his buy menu; his screen in night vision or back
hasDefuserthe 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");
}
PropertyOnType: names
renderModeEntityRenderMode: "normal", "color", "texture", "glow", "alpha", "additive"
renderFxEntityRenderFx: "none", "glowShell", "pulseSlow", "hologram", …
moveTypeEntityMoveType: "none", "walk", "fly", "toss", "noclip", "bounce", "follow", …
solidEntitySolid: "none", "trigger", "box", "slideBox", "bsp"
takeDamageEntityTakeDamage: "no", "yes", "aim"
deadFlagEntityDeadFlag: "alive", "dying", "dead", "respawnable", "discardBody"
waterLevelEntityWaterLevel: "none", "feet", "waist", "head"
waterTypeEntityContents: "empty", "water", "slime", "lava", …
fixAngleEntityFixAngle: "none", "set", "addYaw"
observerMode, observerLastModePlayerObserverMode: "none", "chaseLocked", "chaseFree", "roaming", "inEye", "mapFree", "mapChase"
kevlarPlayerArmorType: "none", "vest", "vestHelmet"
lastHitGroupPlayerHitGroup: "generic", "head", "chest", "stomach", "leftArm", … "shield"
joiningStatePlayerJoinState: "joined", "showMotd", "pickingTeam", …
openMenuPlayerGameMenu: "none", "team", "buy", "buyRifle", "radio1", …
modelNamePlayerPlayerModel: "urban", "gign", "terror", "leet", … "unassigned", "auto"
ignoreGlobalChatPlayerIgnoredChat: "none", "enemy", "all"
throwDirectionPlayerThrowDirection: "none", "forward", "backward", "grenade", …
bloodColorPlayerBloodColor: "none", "red", "yellow"
musicStatePlayerMusicState: "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 without a name
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:

MethodGives
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:

FieldMatches
classnamethe class name: "grenade", "weaponbox", "func_door"
modelthe model: "models/w_c4.mdl"
ownerwhose it is: a grenade's thrower, a weapon's carrier
near, radiusthe 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.kind says which weapon it is, as a name: "knife", "ak47", "awp", "hegrenade", "flashbang", … (the type WeaponKind; an unknown weapon is "none"). weapon.kindId is the same as the game's number.
  • weapon.classname is the weapon's name as the player's methods take it, a WeaponName such as "weapon_ak47": player.give(weapon.classname), player.getAmmo(weapon.classname).
  • player.activeItem, player.lastItem and player.activeItemSent are a Weapon or null; weapon.player is the Player who carries it, or null when it lies on the ground; weapon.next is the next weapon in the same inventory slot. These are read-only.
  • player.items is every weapon the player carries, grenades included; one of them is find: player.items.find(item => item.classname == "weapon_ak47"), null if he has none.
  • weapon.owner is the engine's owner field, an entity index; weapon.player is the one to use for the carrier.
  • The weapon's own members drop their Pawn prefix: m_Weapon_iClip is clip, m_Weapon_flNextPrimaryAttack is nextPrimaryAttack.

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 are player.items);
  • var_controller and var_blending, with get_entvar and set_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 name m_szTeamName (the side is player.team) and armour type var_armortype (the armour is player.armor and player.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 shot m_Weapon_flLastFire (every weapon's is weapon.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.