Intégration au bac à sable
Ce qui fait qu'un addon se comporte comme un outil du jeu : ses créations s'annulent avec la touche Annuler, se nettoient depuis le menu de spawn, passent dans les dupes, apparaissent dans le menu de spawn et le menu contextuel, et peuvent changer le déplacement du joueur. Les noms sont ceux de Garry's Mod : undo, cleanup, duplicator, list, properties, spawnmenu, DForm, CMoveData et CUserCmd.
| Bibliothèque | Côté | Rôle |
|---|---|---|
undo | serveur | Ajouter une entrée à la pile d'annulation du joueur |
cleanup | partagé (Add et CC_* : serveur) | Catégories de l'onglet Nettoyage du menu de spawn |
duplicator | serveur | Entités scriptées et données dans les dupes, copier / coller par code |
list, table.Copy | partagé | Listes nommées partagées entre addons ("SpawnableEntities"…) |
properties | partagé | Actions du menu contextuel sur un objet |
spawnmenu, DForm | client | Onglets du menu de spawn et panneaux d'options d'outils |
CMoveData, CUserCmd, ply:SetMoveType | client (hooks) / serveur (SetMoveType) | Modifier le déplacement du joueur : jetpack, double saut, vol… |
drive | serveur (vue : client) | Piloter une entité avec ses touches |
unpack existe aussi (le même que table.unpack), pour les scripts écrits pour Garry's Mod.
Annulation : undo
Côté serveur. Une entrée se construit entre undo.Create et undo.Finish ; elle va en haut de la pile d'annulation du joueur, avec les props et dupes qu'il fait apparaître lui-même. La touche Annuler (Z par défaut) enlève la dernière entrée : ses entités sont supprimées et ses fonctions appelées. Une entrée dont les entités ont déjà disparu et qui n'a pas de fonction est sautée (on passe à la précédente).
| Fonction | Rôle |
|---|---|
undo.Create(nom) | Commence une entrée ("Undo" si pas de nom). |
undo.AddEntity(ent) | Entité supprimée par l'annulation (prop, entité scriptée, véhicule, PNJ). |
undo.AddFunction(fonction, ...) | Appelée à l'annulation avec fonction(entrée, ...) (les arguments donnés ici). |
undo.SetPlayer(ply) | Le joueur dont la pile reçoit l'entrée. Obligatoire : sans joueur valide, Finish ne fait rien. |
undo.SetCustomUndoText(texte) | Message affiché à l'annulation : « Annulé : texte » (sinon « Annulé : nom »). |
undo.Finish(nom) | Termine l'entrée et la pousse sur la pile (nom facultatif remplace celui de Create). |
undo.GetTable(ply) → table | Entrées Lua encore annulables (de ce joueur, ou de tous sans argument), la plus ancienne en premier. Chaque entrée : Name, Entities, Functions, Owner, CustomUndoText, Id. |
Le hook serveur CanUndo(ply, entrée) est appelé avant chaque annulation, y compris pour les entrées du jeu (un prop, une dupe, un PNJ, une contrainte : Name vaut alors "Prop", "Dupe", "NPC" ou "Constraint", et Entities donne les entités). Renvoyer false garde l'entrée : le joueur lit « Impossible d'annuler : nom. »
-- lua/autorun/caisses.lua (partagé) : /caisse fait apparaître une caisse annulable et nettoyable.
cleanup.Register("caisses") -- des deux côtés : le client en a besoin pour l'onglet Nettoyage
if SERVER then
local function poser_caisse(ply)
local tr = ply:GetEyeTrace()
if not tr.Hit or tr.HitPos:Distance(ply:GetPos()) > 20 then return end
local caisse = ents.SpawnProp("builtin:crate", tr.HitPos + Vector(0, 0.5, 0))
if not caisse then return end
undo.Create("Caisse")
undo.AddEntity(caisse)
undo.AddFunction(function(entree, joueur)
if IsValid(joueur) then joueur:ChatPrint("Ta caisse a été retirée.") end
end, ply)
undo.SetPlayer(ply)
undo.Finish()
cleanup.Add(ply, "caisses", caisse)
end
concommand.Add("caisse", poser_caisse)
-- Le bouton de l'onglet « Caisses » (voir Menu de spawn) envoie ce message.
net.Receive("caisses_poser", function(taille, ply) poser_caisse(ply) end)
-- Pas d'annulation pendant une manche.
hook.Add("CanUndo", "caisses_manche", function(ply, entree)
if GetGlobalBool("manche_en_cours") then return false end
end)
end
ents.SpawnProp ou ents.Create appartient au monde : il ne compte pas dans la limite du joueur. undo et cleanup.Add le rattachent quand même au joueur pour l'annulation et le nettoyage.Nettoyage : cleanup
L'onglet Nettoyage du menu de spawn a un bouton par catégorie (« Tout » en premier), et une deuxième rangée « Tous les joueurs (admin) » pour les admins. Les catégories intégrées :
| Catégorie | Bouton | Ce qui est supprimé (ce que le joueur possède) |
|---|---|---|
"" | Tout | Tous ses props, entités, véhicules, PNJ et contraintes, et tout ce qui est rangé avec cleanup.Add |
"props" | Props | Les props ordinaires (pas les entités scriptées ni les véhicules) |
"vehicles" | Véhicules | Les véhicules |
"npcs" | PNJ | Les PNJ |
"sents" | Entités | Les entités scriptées (hors véhicules) |
"constraints" | Contraintes | Ses contraintes (soudures, cordes…) |
Chaque catégorie supprime aussi les entités rangées sous ce nom avec cleanup.Add. Le joueur lit « Nettoyé (Props) : 3 éléments. » Le hook serveur CanCleanup(ply, catégorie) peut refuser (false : « Nettoyage refusé par le mode de jeu. »). Nettoyer les objets de tous les joueurs demande la permission cleanup.
| Fonction | Côté | Rôle |
|---|---|---|
cleanup.Register(catégorie) | partagé | Ajoute une catégorie (un bouton portant son nom). À appeler des deux côtés (fichier partagé) : le bouton vient de la liste du client. |
cleanup.GetList() → table | partagé | Les catégories ajoutées par Register (pas les intégrées). |
cleanup.Add(ply, catégorie, ent) | serveur | Range une entité (prop, entité scriptée, PNJ) dans une catégorie du joueur. |
cleanup.ReplaceEntity(ancienne, nouvelle) | serveur | Une entité en remplace une autre dans les catégories. |
cleanup.CC_Cleanup(ply, catégorie) | serveur | Nettoie comme le bouton (aussi la forme GMod CC_Cleanup(ply, commande, { "props" })). Sans catégorie : tout. |
cleanup.CC_AdminCleanup(ply, catégorie) | serveur | La même chose pour les objets de tous les joueurs ; ply doit avoir la permission cleanup. |
Dupes : duplicator
Côté serveur. Les dupes gardent les props, leurs contraintes et leurs gadgets. Avec duplicator, elles gardent aussi les entités scriptées et des données posées par les addons.
Entités scriptées dans les dupes
Quand une dupe contient une entité scriptée, le serveur la recrée en Lua :
- Classe enregistrée avec
duplicator.RegisterEntityClass(classe, fonction, noms…): le jeu appellefonction(ply, …)avec une valeur par nom d'argument, et la fonction renvoie l'entité créée. Noms reconnus :"Pos"(Vector),"Ang"ou"Angle"(Angle),"Model","Frozen","Color","Class","Data"(toute la table de l'entrée) ; tout autre nom est un champ de l'entité (ent.Stockpour"Stock"). - Classe non enregistrée : duplication générique (
ents.Create,SetModel,SetPos,SetAngles,SetColor,Spawn), sauf siENT.DisableDuplicator = true: l'entité est alors laissée de côté. - Une classe
ENT.AdminOnly = truen'est recréée que pour un admin.
Les dupes du toolgun sont sauvegardées par le jeu du joueur, qui ne voit que les données envoyées par le réseau. Le serveur copie donc toutes les 0,5 s les champs nommés dans RegisterEntityClass (booléens, nombres, textes, vecteurs, couleurs) dans les données de l'entité ("dupe_fields") : une dupe faite par un joueur les garde.
Modificateurs d'entité
duplicator.StoreEntityModifier(ent, id, données) attache une table à n'importe quel prop (8 Ko au plus par entité, tous modificateurs compris) ; elle voyage avec la dupe (champ "lua_mods" des données du prop). Au collage, la fonction enregistrée avec duplicator.RegisterEntityModifier(id, fonction) est appelée avec fonction(ply, ent, données) : c'est elle qui réapplique l'effet (une donnée sans fonction enregistrée ne fait rien). duplicator.ClearEntityModifier(ent, id) l'enlève.
Hooks et fonctions de l'entité
| Appel | Quand |
|---|---|
hook CanDuplicate(ply, liste) | Avant un collage (toolgun, onglet Dupes, duplicator.Paste). liste : { { Class, Model }, … }. false refuse (« Dupe refusée par le mode de jeu. »). |
hook PreDuplicate(ply, liste) | Juste après, si le collage est accepté. |
ENT:PostEntityPaste(ply, ent, créées) | Après le collage, sur chaque entité scriptée collée ; créées : toutes les entités de la dupe. |
hook PostDuplicate(ply, créées) | Après le collage et les modificateurs. |
ENT:PreEntityCopy() | Avant que duplicator.Copy lise l'entité. |
ply vaut nil pour un collage sans joueur (duplicator.Paste(nil, …)).
Copier et coller par code
| Fonction | Rôle |
|---|---|
duplicator.Copy(ent) → table | L'entité et tout ce qui lui est relié par des contraintes : { Entities = { [n] = { Class, Model, Pos, Angle, Frozen, Color, EntityMods, champs… } }, Constraints = { { Type, Ent1, Ent2, LPos1, LPos2, Axis, Params } }, Mins, Maxs }. Ent1 / Ent2 sont des clés de Entities (Ent2 = 0 : le monde). Positions dans le monde. |
duplicator.Paste(ply, entités, contraintes) → créées, nombre | Recrée ces tables (même forme). Renvoie les entités créées (mêmes clés) et le nombre de contraintes créées. Avec un joueur, elles lui appartiennent et forment une entrée d'annulation. 150 entités au plus, à moins de 150 m de leur centre (n'importe où sur la map). |
duplicator.CreateEntityFromTable(ply, données) → Entity | Recrée une seule entrée de Entities. |
duplicator.FindEntityClass(classe) → table ou nil | { Func, Args } d'une classe enregistrée. |
-- Serveur : /cloner recopie ce que le joueur vise (et ce qui y est soudé) 3 m plus haut.
concommand.Add("cloner", function(ply)
local ent = ply:GetEyeTrace().Entity
if not IsValid(ent) or ent:IsPlayer() then return end
local copie = duplicator.Copy(ent)
for _, entree in pairs(copie.Entities) do
entree.Pos = entree.Pos + Vector(0, 3, 0)
end
local creees, contraintes = duplicator.Paste(ply, copie.Entities, copie.Constraints)
ply:ChatPrint(table.Count(creees) .. " entités, " .. contraintes .. " contraintes.")
end)
Entités dans le menu de spawn
Une entité scriptée avec ENT.Spawnable = true (ou ajoutée à la liste "SpawnableEntities") apparaît dans l'onglet Entités du menu de spawn, rangée par ENT.Category (« Autres » par défaut) sous son ENT.PrintName. Avec ENT.AdminOnly = true, le bouton porte « (admin) » et ne marche que pour les admins.
Un clic la fait apparaître côté serveur là où le joueur vise (120 m au plus) : le hook PlayerSpawnSENT(ply, classe) peut refuser (false), puis ENT:SpawnFunction(ply, tr, classe) la crée si la classe en a une (elle reçoit la trace du regard et renvoie l'entité) ; sinon le jeu la pose sur la surface visée, tournée vers le joueur, et appelle Spawn et Activate. L'entité appartient au joueur, s'annule avec Annuler, puis PlayerSpawnedSENT(ply, ent) est appelé.
Exemple : un coffre duplicable
-- lua/entities/coffre.lua (partagé)
ENT.PrintName = "Coffre à pièces"
ENT.Category = "Mon addon"
ENT.Spawnable = true
ENT.Model = "builtin:crate"
if SERVER then
function ENT:Initialize()
self.Pieces = self.Pieces or 10
end
function ENT:Use(ply)
if self.Pieces <= 0 then return end
self.Pieces = self.Pieces - 1
ply:ChatPrint("Une pièce ! Il en reste " .. self.Pieces .. ".")
end
-- Posé 0,5 m au-dessus de la surface visée.
function ENT:SpawnFunction(ply, tr, classe)
local ent = ents.Create(classe)
ent:SetPos(tr.HitPos + tr.HitNormal * 0.5)
ent:Spawn()
return ent
end
-- Le nombre de pièces passe dans les dupes (copié dans les données de l'entité toutes les 0,5 s).
duplicator.RegisterEntityClass("coffre", function(ply, Pos, Ang, Pieces)
local ent = ents.Create("coffre")
ent:SetPos(Pos)
ent:SetAngles(Ang)
ent.Pieces = Pieces
ent:Spawn()
return ent
end, "Pos", "Ang", "Pieces")
function ENT:PostEntityPaste(ply, ent, creees)
if IsValid(ply) then ply:ChatPrint("Coffre collé avec " .. tostring(self.Pieces) .. " pièces.") end
end
-- Une étiquette sur n'importe quel prop, gardée par les dupes.
duplicator.RegisterEntityModifier("monaddon_etiquette", function(ply, ent, donnees)
ent:SetNWString("etiquette", tostring(donnees.texte or ""))
end)
concommand.Add("etiquette", function(ply, args, texte)
local ent = ply:GetEyeTrace().Entity
if not IsValid(ent) or ent:IsPlayer() then return end
ent:SetNWString("etiquette", texte)
duplicator.StoreEntityModifier(ent, "monaddon_etiquette", { texte = texte })
end)
end
Listes : list
Des tables nommées que les addons remplissent et lisent, des deux côtés (chaque côté a les siennes : remplis-les dans un fichier partagé).
| Fonction | Rôle |
|---|---|
list.Set(nom, clé, valeur) | Range une valeur sous une clé. |
list.Add(nom, valeur) → index | Ajoute à la suite. |
list.Get(nom) → table | Une copie de la liste (la modifier ne change pas la liste). |
list.GetForEdit(nom) → table | La liste elle-même (créée vide au besoin). |
list.HasEntry(nom, clé), list.Contains(nom, valeur) → booléen | La clé existe ; la valeur est dans la liste. |
list.GetTable() → table | Les noms des listes existantes. |
table.Copy(t) → table | Copie profonde d'une table (16 niveaux au plus). |
Le jeu lit la liste "SpawnableEntities" : list.Set("SpawnableEntities", classe, { PrintName = "…", ClassName = classe, Category = "…", AdminOnly = false }) ajoute une entité scriptée existante à l'onglet Entités sans toucher à son fichier.
Menu contextuel : properties
Le joueur maintient la touche Menu contextuel (N par défaut, réglable dans Paramètres → Touches ; le C de Garry's Mod) : la souris est libre, l'objet sous le curseur s'éclaire, et un clic sur lui ouvre la liste de ses actions. Relâcher la touche (ou Échap) ferme le menu. Le gamemode Sandbox fournit "freeze" (Figer / libérer) et "remove" (Supprimer) ; les addons en ajoutent avec properties.Add.
properties.Add(nom, table) se fait dans un fichier partagé : le client construit le menu, le serveur reçoit l'action.
| Champ | Rôle |
|---|---|
MenuLabel | Texte dans le menu (sinon le nom). |
Order | Rang dans le menu, du plus petit au plus grand (0 par défaut ; à égalité, par nom). |
PrependSpacer | true : un séparateur avant cette action. |
MenuIcon | Accepté pour la compatibilité (pas encore affiché). |
Filter(self, ent, ply) → booléen | L'action est-elle proposée pour cet objet ? Par défaut properties.CanBeTargeted(ent, ply). |
Action(self, ent, tr) | Client : l'action est choisie. tr : la trace sous le curseur (HitPos, HitNormal, Entity…). |
Receive(self, taille, ply) | Serveur : reçoit le message envoyé par self:MsgStart() … self:MsgEnd() dans Action (entre les deux, les net.Write… habituels). |
properties.CanBeTargeted(ent, ply) est vrai pour une entité valide qui n'est pas un joueur et que ply peut toucher au toolgun selon la protection des props. properties.List contient les propriétés par nom. Côté client, properties.GetHovered() donne l'entité sous la souris (ou sous le viseur quand la souris tient la caméra). Le hook CanProperty(ply, nom, ent) est demandé par les propriétés de Sandbox (des deux côtés) : false les cache et les refuse. Les hooks client ContextMenuOpen (false empêche l'ouverture), OnContextMenuOpen et OnContextMenuClose suivent le menu.
-- lua/autorun/peinture.lua (partagé)
properties.Add("peindre_rouge", {
MenuLabel = "Peindre en rouge",
Order = 500,
Filter = function(self, ent, ply)
return properties.CanBeTargeted(ent, ply) and hook.Run("CanProperty", ply, self.InternalName, ent) ~= false
end,
Action = function(self, ent, tr)
self:MsgStart()
net.WriteEntity(ent)
self:MsgEnd()
end,
Receive = function(self, taille, ply)
local ent = net.ReadEntity()
-- Toujours revérifier côté serveur : le message vient d'un joueur.
if self:Filter(ent, ply) then ent:SetColor(Color(255, 0, 0)) end
end,
})
Menu de spawn : spawnmenu
Côté client. Le menu de spawn montre, dans l'ordre : les onglets du jeu et des addons de props, Entités (s'il y a des entités Spawnable), les onglets créés en Lua, puis Nettoyage, Dupes et Protection.
Onglets de création
spawnmenu.AddCreationTab(nom, fonction, icône, ordre, infobulle) ajoute un onglet titré nom. fonction() doit renvoyer un panneau vgui : il est créé une seule fois, à la première ouverture du menu, puis gardé (avec son état) tant que les scripts tournent. Les onglets sont triés par ordre (1000 par défaut) puis par nom ; infobulle s'affiche au survol de l'onglet ; icône est gardée pour la compatibilité. spawnmenu.GetCreationTabs() donne les onglets ajoutés.
-- lua/autorun/client/onglet_caisses.lua : un onglet avec un bouton (reçu par le serveur dans l'exemple undo).
spawnmenu.AddCreationTab("Caisses", function()
local page = vgui.Create("DPanel")
page:DockPadding(10, 10, 10, 10)
local titre = vgui.Create("DLabel", page)
titre:SetText("Caisses rapides")
titre:Dock(TOP)
local bouton = vgui.Create("DButton", page)
bouton:SetText("Poser une caisse")
bouton:SetTall(36)
bouton:Dock(TOP)
bouton.DoClick = function()
net.Start("caisses_poser")
net.SendToServer()
end
return page
end, nil, 50, "Des caisses annulables")
Options d'outils
Les onglets, catégories et options d'outils créés en Lua s'affichent sous les outils du jeu, dans la liste des outils du menu de spawn : le nom de l'onglet en titre, puis ses catégories et leurs options. Choisir une option affiche son panneau à côté : CPanelFunction(panneau) reçoit un DForm neuf à chaque fois, puis la commande de l'option est lancée.
| Fonction | Rôle |
|---|---|
spawnmenu.AddToolTab(nom, titre, icône) → onglet | Crée (ou retrouve) un onglet d'outils. |
spawnmenu.AddToolCategory(onglet, id, titre) | Ajoute une catégorie dans un onglet (créé au besoin). |
spawnmenu.AddToolMenuOption(onglet, catégorie, id, titre, commande, config, fonction, options) | Ajoute une option. fonction(panneau) remplit son panneau. commande : "gmod_tool <outil>" prend le toolgun sur cet outil ; "nom valeur" change la ConVar nom si elle existe (sinon rien) ; "" : rien. |
spawnmenu.GetToolMenu(onglet), spawnmenu.GetTools() | Les catégories d'un onglet ; tous les onglets. |
Les hooks client AddToolMenuTabs, AddToolMenuCategories et PopulateToolMenu sont appelés, dans cet ordre, une fois par lancement des scripts, quand le menu en a besoin pour la première fois : ajoute-les au chargement du fichier.
-- lua/autorun/client/jetpack_menu.lua : réglages du jetpack (exemple suivant) dans la liste des outils.
hook.Add("PopulateToolMenu", "jetpack_menu", function()
spawnmenu.AddToolMenuOption("Options", "Jetpack", "jetpack_reglages", "Réglages du jetpack", "", "", function(panneau)
panneau:Help("Maintiens Courir et Saut pour t'envoler.")
panneau:CheckBox("Activer le jetpack", "jetpack_actif")
panneau:NumSlider("Poussée (m/s²)", "jetpack_poussee", 5, 40, 0)
end)
end)
Déplacement du joueur
Chaque joueur calcule son propre déplacement (le serveur le vérifie). Les hooks de déplacement tournent donc côté client, pour le joueur local, à chaque image physique (60 fois par seconde), dans cet ordre :
StartCommand(ply, cmd)puisCreateMove(cmd): les commandes du tick (CUserCmd).SetupMove(ply, mv, cmd)puisMove(ply, mv), avant que le jeu déplace le joueur (CMoveData). SiMoverenvoietrue, le jeu ne déplace pas le joueur : la vitesse et la position demvdeviennent son état (sans collisions : c'est au hook de les gérer).- Le jeu déplace le joueur, puis
FinishMove(ply, mv)avec sa nouvelle vitesse et position (modifiables).
Rien n'est appelé (aucun coût) tant qu'aucun hook ni méthode du gamemode (GM:SetupMove…) n'utilise ces événements. Ils ne sont pas appelés quand le joueur est mort, bloqué (ply:Freeze) ou en MOVETYPE_NONE.
Unités du jeu : mètres, m/s, degrés. Les boutons sont les bits IN_*. Ce qui agit sur le jeu : le bit IN_JUMP (l'ajouter au sol fait sauter, l'enlever annule le saut), les vitesses avant / côté (bornées à la vitesse max), la vitesse, la position, la vitesse max (pour ce tick) et les angles de vue de cmd. Les autres bits, la vitesse verticale et les angles de mv se lisent mais ne changent rien. Une vitesse vers le haut décolle le joueur du sol.
-- lua/autorun/jetpack.lua (partagé : le hook côté serveur coupe les contrôles de vitesse et de vol)
if CLIENT then
CreateClientConVar("jetpack_actif", 1, true, false, "Jetpack (Courir + Saut)", 0, 1)
CreateClientConVar("jetpack_poussee", 20, true, false, "Poussée du jetpack", 5, 40)
end
-- Appelé seulement chez le client, pour son propre joueur.
hook.Add("SetupMove", "jetpack", function(ply, mv, cmd)
if not GetConVar("jetpack_actif"):GetBool() then return end
if not (mv:KeyDown(IN_SPEED) and mv:KeyDown(IN_JUMP)) then return end
local v = mv:GetVelocity()
local poussee = GetConVar("jetpack_poussee"):GetFloat()
mv:SetVelocity(Vector(v.x, math.min(v.y + poussee / 60, 8), v.z))
end)
Piloter une entité : drive
Côté serveur, drive.PlayerStartDriving(ply, ent, mode) fait piloter une entité au joueur : il reste figé sur place, ses touches et sa vue déplacent l'entité (figée elle aussi, donc sans physique), et sa caméra passe derrière elle en 3e personne. La touche Utiliser (E) ou drive.PlayerStopDriving(ply) arrête. Des deux côtés, ply:IsDrivingEntity(), ply:GetDrivingEntity(), ply:GetDrivingMode() et drive.GetMethod(ply) (l'objet du mode en cours : self.Player, self.Entity).
Modes fournis : "drive_noclip" (l'entité vole où le joueur regarde, 6 m/s, 20 m/s avec Courir ; Saut monte, Accroupi descend) et "drive_base" (ne bouge pas tout seul). drive.Register(nom, table, base) en ajoute un ; ses fonctions sont appelées à chaque image physique côté serveur : Init(), StartMove(mv, cmd), Move(mv), FinishMove(mv) (celle de drive_base place l'entité à mv:GetOrigin()), Stop(), et côté client CalcView(view) (modifie view.origin, view.angles). mv et cmd sont les mêmes objets que pour le déplacement du joueur, avec la position et la vitesse de l'entité.
-- Partagé : une action « Piloter » dans le menu contextuel.
properties.Add("piloter", {
MenuLabel = "Piloter",
Order = 600,
Action = function(self, ent)
self:MsgStart()
net.WriteEntity(ent)
self:MsgEnd()
end,
Receive = function(self, taille, ply)
local ent = net.ReadEntity()
if self:Filter(ent, ply) then drive.PlayerStartDriving(ply, ent, "drive_noclip") end
end,
})
Type de déplacement
Côté serveur, ply:SetMoveType(MOVETYPE_…) choisit comment un joueur bouge ; ply:GetMoveType() le lit (des deux côtés). Tant qu'un type est imposé, la touche noclip est ignorée.
| Constante | Effet |
|---|---|
MOVETYPE_WALK | Normal (par défaut) : rend la main au jeu et à la touche noclip. |
MOVETYPE_NONE | Ne bouge plus (il peut regarder autour). |
MOVETYPE_FLY | Vole vers où il regarde (Saut monte, Accroupi descend) en butant sur les murs. |
MOVETYPE_NOCLIP, MOVETYPE_OBSERVER | Noclip : vole à travers les murs. |
Les autres constantes de Garry's Mod (MOVETYPE_ISOMETRIC, STEP, FLYGRAVITY, VPHYSICS, PUSH, LADDER, CUSTOM) existent et se lisent avec GetMoveType, mais bougent comme WALK. Sans type imposé, GetMoveType renvoie MOVETYPE_NOCLIP en noclip, sinon MOVETYPE_WALK.
-- Serveur : /voler fait voler un admin (contre les murs), /voler encore le fait marcher.
concommand.Add("voler", function(ply)
if not ply:IsAdmin() then return end
if ply:GetMoveType() == MOVETYPE_FLY then
ply:SetMoveType(MOVETYPE_WALK)
else
ply:SetMoveType(MOVETYPE_FLY)
end
end)