Monde et physique
Angles et vecteurs, traces (rayons), repères et physique des entités (forme de collision, frottement, rebond, collisions au cas par cas), attacher un prop à un autre, réagir aux chocs, chercher des entités, créer des contraintes (soudures, cordes, treuils, vérins, moteurs…), explosions, date et heure, changement de map.
Angles et vecteurs
Un angle est un Vector(pitch, yaw, roll) en degrés ; Angle(p, y, r) le crée. C'est le format renvoyé par ent:GetAngles() et ply:EyeAngles(). Convention du jeu : avant = -Z, Y en haut, pitch positif = vers le haut, yaw positif = vers la gauche (rotation autour de Y).
| Angle | Résultat |
|---|---|
ang.p / ang.pitch, ang.y / ang.yaw, ang.r / ang.roll | Les composantes (= x, y, z) |
ang:Forward(), ang:Right(), ang:Up() | Vecteurs unitaires (avant, droite, haut) |
ang:RotateAroundAxis(axe, degrés) | Nouvel angle tourné autour de l'axe (un Vector) |
vec:Angle() | Angle qui regarde dans la direction du vecteur (roll 0) |
vec:Rotate(ang) | Nouveau vecteur tourné par l'angle |
Les vecteurs ont aussi les noms de Garry's Mod : :Length(), :LengthSqr(), :Length2D() (sur le sol, x et z), :Distance(v), :DistToSqr(v), :Dot(v), :Cross(v), :GetNormalized(), :IsZero(), :IsEqualTol(v, tolérance), :WithinAABox(min, max). Les méthodes Godot (length(), normalized(), distance_to()…) restent disponibles. Un vecteur ne se modifie jamais « en place » : chaque méthode renvoie un nouveau vecteur.
local ang = ply:EyeAngles()
local devant = ply:EyePos() + ang:Forward() * 2 -- 2 m devant les yeux
local cap = (cible - ply:GetPos()):Angle().y -- yaw pour regarder la cible
Aides mathématiques
| Fonction | Rôle |
|---|---|
Lerp(t, a, b), LerpVector(t, a, b) | Mélange de a (t = 0) à b (t = 1) |
LerpAngle(t, a, b) | Pareil pour des angles, par le plus court chemin |
math.Clamp(v, min, max), math.Round(v, décimales) | Borne une valeur, arrondit |
math.Approach(actuel, cible, pas) | Avance vers la cible d'au plus pas |
math.Remap(v, a, b, c, d) | Passe v de l'intervalle a–b à c–d |
math.Distance(x1, y1, x2, y2) | Distance entre deux points 2D |
math.NormalizeAngle(a) | Ramène un angle entre -180 et 180 |
VectorRand(min, max), AngleRand() | Vecteur (composantes entre -1 et 1 par défaut) et angle au hasard |
Traces
Une trace lance un rayon (ou une boîte) entre deux points et dit ce qu'il touche. Des deux côtés.
util.TraceLine(options) → table
Options : start, endpos (des Vector), filter, mask, ignoreworld. Longueur 20 000 m au plus. Résultat :
| Champ | Valeur |
|---|---|
Hit | true si le rayon a touché quelque chose |
HitPos, HitNormal | Point touché, orientation de la surface |
Fraction | Part du trajet parcourue (0 à 1) |
Entity | Prop ou joueur touché, sinon nil |
HitWorld, HitNonWorld | Touché la map / un prop ou un joueur |
HitSky | Toujours false |
StartPos, Normal | Départ, direction du rayon |
StartSolid | Déjà bloqué au départ |
-- Le prop sous le viseur, en ignorant le joueur.
local tr = util.TraceLine({ start = ply:EyePos(), endpos = ply:EyePos() + ply:GetAimVector() * 10, filter = ply })
if tr.Hit and IsValid(tr.Entity) then print("Je vise", tr.Entity:GetModel()) end
util.TraceHull(options) → table
Comme TraceLine, mais avec une boîte : mins et maxs (autour du point) en plus. StartSolid vaut true si la boîte est déjà bloquée au départ (pratique pour vérifier qu'un joueur tient à un endroit).
Autres aides
util.QuickTrace(origine, décalage, filtre):TraceLinedeorigineàorigine + décalage.util.IsInWorld(pos):truesi la position n'est pas dans un mur et qu'il y a du sol en dessous.
Filtres et masques
filter : une entité ou un joueur (ignoré), une table de ceux-ci, ou une fonction function(ent) return true end (true = on touche cet objet, false = on passe à travers ; le monde n'est jamais filtré ; 16 objets traversés au plus).
| Masque | Ce qui est touché |
|---|---|
MASK_SOLID (défaut), MASK_SHOT | Monde, joueurs, props, ragdolls |
MASK_PLAYERSOLID | Monde, joueurs, props |
MASK_SOLID_BRUSHONLY, MASK_VISIBLE | Le monde seul |
MASK_ALL | Tout, y compris les props « débris » (voir groupes de collision) |
ignoreworld = true retire le monde.
Repères des entités
Ces méthodes existent des deux côtés, sur les props, les entités scriptées et les véhicules.
| Méthode | Renvoie |
|---|---|
ent:GetForward(), ent:GetRight(), ent:GetUp() | Directions de l'entité (vecteurs unitaires) |
ent:LocalToWorld(v), ent:WorldToLocal(v) | Position convertie entre le repère de l'entité et le monde |
ent:LocalToWorldAngles(ang), ent:WorldToLocalAngles(ang) | Pareil pour un angle |
ent:OBBMins(), ent:OBBMaxs(), ent:OBBCenter() | Boîte du modèle dans son repère |
ent:BoundingRadius() | Rayon de la sphère qui l'englobe |
ent:GetVelocity() | Vitesse en m/s (serveur : exacte ; client : estimée d'après le mouvement) |
ent:GetAngularVelocity() | Vitesse de rotation en degrés/s (serveur) |
ent:GetCollisionGroup(), ent:IsSolid(), ent:GetNoDraw(), ent:GetMaterial(), ent:GetModelScale(), ent:GetParent() | Réglages décrits plus bas |
-- Une caisse 1 m devant et 0,5 m au-dessus d'un prop, tournée comme lui.
local pos = prop:LocalToWorld(Vector(0, 0.5, -1))
local caisse = ents.SpawnProp("builtin:crate", pos)
caisse:SetAngles(prop:GetAngles())
Physique
Vitesse et forces serveur
| Méthode | Effet |
|---|---|
ent:SetVelocity(v) | Remplace la vitesse (m/s, 500 au plus) |
ent:SetAngularVelocity(v) | Vitesse de rotation (degrés/s) |
ent:ApplyForce(impulsion) | Pousse au centre (N·s) |
ent:ApplyForceOffset(impulsion, posMonde) | Pousse à un point du monde (fait tourner) |
ent:ApplyTorque(impulsion) (alias ApplyTorqueCenter) | Impulsion de rotation |
ent:SetGravityEnabled(bool) | Active ou coupe la gravité |
ent:SetOwner(ply ou nil) | Change le propriétaire (GetOwner, nettoyage, limites) |
Objet physique
ent:GetPhysicsObject() renvoie un objet à la manière de Garry's Mod, pour reprendre du code existant. Ses méthodes sont des raccourcis vers celles de l'entité ; les modifications n'existent que côté serveur.
| Lecture (partagé) | Modification (serveur) |
|---|---|
:IsValid(), :GetEntity(), :GetMass(), :GetVelocity(), :GetAngleVelocity(), :GetPos(), :GetAngles(), :IsMotionEnabled(), :GetMaterial(), :GetDamping(), :IsAsleep() | :SetMass(kg), :SetVelocity(v), :AddVelocity(v), :SetAngleVelocity(v), :AddAngleVelocity(v), :ApplyForceCenter(impulsion), :ApplyForceOffset(impulsion, pos), :ApplyTorqueCenter(couple), :EnableMotion(bool) (libère / fige), :EnableGravity(bool), :EnableCollisions(bool), :Wake(), :Sleep(), :SetPos(v), :SetAngles(ang), :SetMaterial(nom), :SetDamping(linéaire, angulaire), :EnableDrag(bool), :SetBuoyancyRatio(r) |
Matériau, freinage, flottaison et repos :
| Méthode | Effet |
|---|---|
:SetMaterial(nom) / :GetMaterial() | Matériau physique d'après un nom de surface de Garry's Mod : un nom qui contient ice glisse (plastique, frottement 0,03), bouncy rebondit (caoutchouc, rebond 0,9), rubber donne du caoutchouc, metal / chain / grate du métal, wood du bois, concrete / brick / rock / stone / gravel / dirt du béton ; le reste, du plastique. Remet à zéro SetFriction / SetElasticity. GetMaterial renvoie le matériau de PhysiCore ("plastic", "metal", "wood", "concrete", "rubber"…). Ce n'est pas l'apparence (ent:SetMaterial). |
:SetDamping(linéaire, angulaire) / :GetDamping() | Freinage dans l'air du déplacement et de la rotation (0 à 100, 0 par défaut). GetDamping renvoie les deux valeurs. |
:EnableDrag(bool) | true = freinage de 0,1 / 0,1 ; false = aucun. |
:SetBuoyancyRatio(r) | Flottaison dans l'eau, de 0 (coule) à 1 (flotte très haut) ; vers 0,5 l'objet reste entre deux eaux. Sans appel, elle dépend de la densité du modèle. |
:IsAsleep(), :Sleep(), :Wake() | Le prop est-il au repos (immobile ou figé) ? Le met au repos / le réveille (sans effet sur un prop figé). |
Les lectures (GetDamping, IsAsleep, ent:GetFriction…) sont exactes côté serveur, qui simule les props.
-- /saut : envoie le prop visé en l'air en le faisant tourner.
concommand.Add("saut", function(ply)
local ent = ply:GetEyeTrace().Entity
if not IsValid(ent) or ent:IsPlayer() then return end
local phys = ent:GetPhysicsObject()
phys:EnableMotion(true)
phys:AddVelocity(Vector(0, 8, 0))
phys:AddAngleVelocity(Vector(0, 360, 0))
end)
Collisions et apparence serveur
| Méthode | Effet |
|---|---|
ent:SetCollisionGroup(groupe) | COLLISION_GROUP_NONE : normal. COLLISION_GROUP_DEBRIS, DEBRIS_TRIGGER, WORLD : ne touche que la map. COLLISION_GROUP_INTERACTIVE_DEBRIS, WEAPON, PASSABLE_DOOR : touche la map et les props, les joueurs passent au travers. Les traces par défaut (MASK_SOLID) ne voient plus ces props ; MASK_ALL oui. |
ent:SetNotSolid(bool) | Plus aucune collision (le prop tombe à travers le sol s'il n'est pas figé) |
ent:SetNoDraw(bool) | Invisible (garde ses collisions). Pour la transparence : ent:SetColor(Color(r, g, b, alpha)) avec alpha < 255. |
ent:SetMaterial(id) | Matériau de l'outil Matériau ("metal", "chrome", "gold"… la liste s'affiche dans l'erreur si l'id est inconnu) ; "" revient au matériau d'origine |
ent:SetModelScale(échelle) | Taille de 0,05 à 20, modèle et collisions ensemble, instantané |
-- Un fantôme : transparent, doré, les joueurs passent au travers.
local f = ents.SpawnProp("builtin:barrel", Vector(0, 1, 0))
f:SetMaterial("gold")
f:SetColor(Color(255, 255, 255, 120))
f:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
f:SetModelScale(1.5)
Forme et solidité serveur
Par défaut, une entité collisionne avec les formes de son modèle. Ces méthodes la remplacent par une forme simple ou la rendent traversable. Le changement s'applique chez tous les joueurs (ils butent aussi sur la nouvelle forme). Unités : mètres, dans le repère de l'entité.
| Méthode | Effet |
|---|---|
ent:SetSolid(SOLID_…) | SOLID_NONE : plus aucune collision (comme SetNotSolid(true)). SOLID_BBOX ou SOLID_OBB : une seule boîte, celle de SetCollisionBounds si elle est réglée, sinon la boîte du modèle. SOLID_VPHYSICS : les formes du modèle (normal). |
ent:GetSolid() (partagé) | SOLID_NONE, SOLID_BBOX (forme en boîte) ou SOLID_VPHYSICS. |
ent:PhysicsInit(SOLID_…) | Revient aux formes du modèle (SOLID_NONE : sans collision). Renvoie true. |
ent:PhysicsInitBox(mins, maxs) | Une boîte de coins mins / maxs (±100 m au plus). Renvoie true. |
ent:PhysicsInitSphere(rayon) | Une sphère centrée sur l'entité (0,01 à 100 m) : elle roule. Renvoie true. |
ent:PhysicsDestroy() | Plus de physique : l'entité est figée et sans collision. |
ent:SetFriction(f) / ent:GetFriction() | Frottement de 0 (glisse) à 10 ; 1 par défaut pour la plupart des modèles. |
ent:SetElasticity(e) / ent:GetElasticity() | Rebond de 0 (aucun) à 1. |
Constantes : SOLID_NONE (0), SOLID_BSP (1), SOLID_BBOX (2), SOLID_OBB (3), SOLID_OBB_YAW (4), SOLID_CUSTOM (5), SOLID_VPHYSICS (6) ; FSOLID_NOT_SOLID (4) et FSOLID_TRIGGER (8) existent pour reprendre du code de Garry's Mod.
-- Une caisse qui roule comme une boule et rebondit.
local c = ents.SpawnProp("builtin:crate", Vector(0, 3, 0))
c:PhysicsInitSphere(0.5)
c:SetElasticity(0.8)
c:SetFriction(0.2)
Collisions au cas par cas : ShouldCollide
ent:SetCustomCollisionCheck(true) serveur fait demander au hook ShouldCollide(ent1, ent2) si l'entité doit heurter un autre corps : ent1 est l'entité réglée, ent2 un prop, un joueur, un PNJ, ou un élément du décor de la map (qui n'est pas une entité : vérifie avec istable avant d'appeler ses méthodes). La question est posée quand le corps s'approche à moins de 50 cm environ de l'entité, sur chaque machine (chaque joueur simule son propre déplacement) : ajoute le hook dans un fichier partagé. Renvoyer false : les deux se traversent, jusqu'à ce que la règle soit remise en question. Autre chose : ils se heurtent, et la question sera reposée la prochaine fois que le corps reviendra.
Après avoir changé ta règle, appelle ent:CollisionRulesChanged() (serveur) : les réponses déjà données sont oubliées et le hook est redemandé. ent:GetCustomCollisionCheck() lit le réglage ; SetCustomCollisionCheck(false) l'enlève et rend les collisions normales.
-- Partagé : les joueurs de l'équipe 1 traversent les portes de leur équipe.
hook.Add("ShouldCollide", "portes_equipe", function(ent, autre)
if ent:GetClass() == "porte_equipe" and istable(autre) and autre.IsPlayer and autre:IsPlayer() and autre:Team() == 1 then
return false
end
end)
-- Serveur, à la création de la porte :
-- porte:SetCustomCollisionCheck(true)
Attacher : SetParent
ent:SetParent(ent ou ply ou nil) serveur : le prop suit l'entité ou le joueur en gardant sa position relative actuelle (il est figé tant qu'il est attaché). nil le détache et il retombe. Si le parent disparaît, il est détaché tout seul. ent:GetParent() renvoie le parent.
-- Un ballon qui suit le joueur au-dessus de sa tête.
local ballon = ents.SpawnProp("builtin:ball", ply:GetPos() + Vector(0, 2.5, 0))
ballon:SetParent(ply)
Réagir aux chocs : PhysicsCollide
ENT:PhysicsCollide(data) (entités scriptées, serveur) est appelé quand l'entité heurte un prop ou un joueur (au plus toutes les 0,05 s). Seulement si la classe définit PhysicsCollide : sinon les chocs ne sont pas suivis.
Champ de data | Valeur |
|---|---|
HitEntity | Le prop ou le joueur heurté |
HitPos, HitNormal | Point de contact (approximatif), normale |
OurOldVelocity, TheirOldVelocity | Vitesses avant le choc |
Speed, HitSpeed | Vitesse, vitesse relative |
DeltaTime | 0 |
-- entities/vase.lua : un vase qui se brise quand il tombe fort.
ENT.Model = "builtin:cylinder"
if SERVER then
function ENT:PhysicsCollide(data)
if data.Speed > 6 then
self:EmitSound("physicore/clic.wav")
self:Remove()
end
end
end
Chercher des entités
| Fonction | Renvoie |
|---|---|
ents.FindInSphere(pos, rayon) | Props et joueurs à moins de rayon mètres |
ents.FindInBox(min, max) | Props et joueurs dans la boîte (1024 au plus) |
ents.FindInCone(origine, direction, distance, cosAngle) | Props et joueurs dans un cône (cosAngle 0,7 ≈ cône de 45°) |
ents.FindByModel(id) | Props de ce modèle |
ents.FindByName(nom) | Entités et zones placées dans une map (voir Points, zones et entités de map) |
ents.GetByIndex(ent:EntIndex()) | L'entité de cet identifiant |
player.GetBySteamID(id), player.GetBySteamID64(id) | Le joueur (id en texte, comme ply:SteamID()), ou nil |
-- Qui est devant moi, à moins de 20 m ?
for _, ent in ipairs(ents.FindInCone(ply:EyePos(), ply:GetAimVector(), 20, 0.9)) do
if ent:IsPlayer() and ent ~= ply then ply:ChatPrint(ent:Nick() .. " est devant toi") end
end
Contraintes
La bibliothèque constraint serveur crée les mêmes liens que le toolgun : ils sont dessinés chez tous (cordes) et sauvés dans les dupes. Les arguments suivent Garry's Mod ; les « bones » sont ignorés (mets 0). Les positions locales sont dans le repère de chaque entité. ent2 = nil accroche au monde. Chaque fonction renvoie l'identifiant de la contrainte (un nombre) ou nil.
| Fonction | Lien |
|---|---|
constraint.Weld(ent1, ent2, 0, 0, 0, nocollide) | Soudure |
constraint.NoCollide(ent1, ent2) | Les deux ne se touchent plus |
constraint.Rope(ent1, ent2, 0, 0, lpos1, lpos2, longueur, ajout, 0, largeur) | Corde (longueur ≤ 0 : distance actuelle) |
constraint.Elastic(ent1, ent2, 0, 0, lpos1, lpos2, force, amortissement) | Élastique |
constraint.Axis(ent1, ent2, 0, 0, lpos1, lpos2, 0, 0, 0, nocollide, axeLocal) | Axe (roue, charnière) |
constraint.Ballsocket(ent1, ent2, 0, 0, lpos) | Rotule |
constraint.Slider(ent1, ent2, 0, 0, lpos1, lpos2) | Glissière |
constraint.Keepupright(ent, nil, 0, force) | Garde l'entité droite |
constraint.AdvBallsocket(ent1, ent2, 0, 0, lpos1, lpos2, 0, 0, xmin, ymin, zmin, xmax, ymax, zmax, 0, 0, 0, rotationSeule, nocollide) | Rotule avec des limites d'angle par axe (degrés, -180 à 180 ; -180 / 180 = libre). rotationSeule = true : l'écart entre les deux points n'est plus tenu, seule la rotation est limitée |
constraint.Pulley(ent1, ent2, 0, 0, lpos1, lpos2, poulie1, poulie2) | Poulie : une corde de ent1 à poulie1, puis de poulie2 à ent2 (poulies : positions dans le monde), de longueur totale fixe : quand un côté descend, l'autre monte. Deux entités obligatoires |
Contraintes pilotées au pavé numérique
Ces contraintes bougent quand le joueur ply (le propriétaire, celui dont on écoute les touches) appuie sur des touches du pavé numérique (KEY_PAD_0 à KEY_PAD_9…), comme celles du toolgun. Longueurs en mètres (0,05 à 100), vitesses en m/s. Les arguments de Garry's Mod sans effet (os, largeur, matériau…) gardent leur place : mets 0.
| Fonction | Lien |
|---|---|
constraint.Winch(ply, ent1, ent2, 0, 0, lpos1, lpos2, 0, toucheRentrer, toucheSortir, vitesse) | Treuil : une corde qui raccourcit tant que toucheRentrer est tenue et s'allonge avec toucheSortir (vitesse 0,05 à 20 m/s, 1,5 par défaut ; longueur de 0,1 à 100 m). Longueur de départ : la distance actuelle |
constraint.Hydraulic(ply, ent1, ent2, 0, 0, lpos1, lpos2, longueur1, longueur2, 0, touche, 0, vitesse, 0, bascule) | Vérin : pousse et tire pour tenir longueur1 ; touche tenue, il passe à longueur2 à vitesse m/s, et revient quand on la lâche. bascule = true : chaque appui passe d'une longueur à l'autre |
constraint.Muscle(ply, ent1, ent2, 0, 0, lpos1, lpos2, longueur1, longueur2, 0, touche, 0, période, 0, démarré) | Muscle : va et vient entre longueur1 et longueur2 (un aller-retour en période secondes, 0,05 à 60). La touche le met en marche ou l'arrête ; démarré = true : il bouge tout de suite |
constraint.Motor(ent1, ent2, 0, 0, lpos1, lpos2, 0, couple, 0, nocollide, bascule, ply, 0, toucheAvant, toucheArrière, sens, axe) | Moteur : un axe (comme constraint.Axis, autour de axe, un Vector dans le repère de ent1, Y par défaut) qui tourne tant qu'une touche est tenue. couple de 1 à 100 000 (800 par défaut). bascule = true : un appui le lance, un autre l'arrête. sens = -1 inverse la rotation. Attention : ici ply est le 12e argument, comme dans Garry's Mod |
Pour constraint.FindConstraints et RemoveConstraints, leurs types sont "winch", "hydraulic", "muscle", "pulley" et "advballsocket" ; un moteur est un "axis". Elles sont dessinées chez tous et gardées dans les dupes, comme les autres.
-- Un vérin entre une base figée et une plateforme : 1,5 m au repos, 3 m tant que le joueur tient 8 du pavé numérique.
local base = ents.SpawnProp("builtin:crate", ply:GetPos() + Vector(0, 0.5, -4))
base:SetFrozen(true)
local plateforme = ents.SpawnProp("builtin:crate", base:GetPos() + Vector(0, 1.5, 0))
constraint.Hydraulic(ply, base, plateforme, 0, 0, Vector(0, 0, 0), Vector(0, 0, 0), 1.5, 3, 0, KEY_PAD_8, 0, 1)
Gestion : constraint.Remove(id), constraint.RemoveAll(ent) (renvoie le nombre retiré), constraint.RemoveConstraints(ent, "weld"), constraint.FindConstraints(ent, type) → liste de {Index, Type, Ent1, Ent2} (type facultatif), constraint.GetAllConstrainedEntities(ent) → toutes les entités reliées (ent compris).
local a = ents.SpawnProp("builtin:crate", Vector(0, 1, 0))
local b = ents.SpawnProp("builtin:crate", Vector(0, 2, 0))
constraint.Weld(a, b)
constraint.Rope(a, nil, 0, 0, Vector(0, 0.45, 0), Vector(0, 6, 0), 0, 0.5) -- pend à 6 m du sol
Explosions
util.BlastDamage(pos, rayon, dégâts, attaquant, force) serveur : dégâts aux joueurs avec ligne de vue, qui baissent avec la distance ; pousse les props ; effet visible et hook WeaponExplosion. Rayon de 0,1 à 100 m ; attaquant et force sont facultatifs. Respecte le réglage « dégâts entre joueurs ».
-- Un baril qui explose quand on le supprime.
hook.Add("EntityRemoved", "baril_explosif", function(ent)
if SERVER and ent:GetModel() == "builtin:barrel" then
util.BlastDamage(ent:GetPos(), 6, 80)
end
end)
Changer de map
game.ChangeMap(id, gamemode) serveur change de map (et de gamemode, facultatif) à la fin de l'image ; les scripts actuels s'arrêtent (hook ShutDown). Renvoie false si la map n'existe pas. game.GetMapList() donne les ids : maps intégrées (builtin:…) et .pcmap des addons activés (addon:…/maps/…pcmap).
concommand.Add("prochaine", function(ply)
if not ply:IsAdmin() then return end
local maps = game.GetMapList()
game.ChangeMap(maps[math.random(#maps)])
end)
Date et heure : os
Une version sûre de os, des deux côtés. Rien d'autre (pas de fichiers, pas de commandes).
| Fonction | Renvoie |
|---|---|
os.time() | Secondes Unix réelles |
os.time({year, month, day, hour, min, sec}) | Secondes Unix de cette date (heure locale) |
os.clock(), os.difftime(a, b) | Temps processeur, différence en secondes |
os.date(format, temps) | Texte daté : %Y %y %m %d %H %I %M %S %p %A %a %B %b %w %j %c %x %X %% (jours et mois en français). "*t" → table {year, month, day, hour, min, sec, wday (1 = dimanche), yday, isdst}. Préfixe ! = heure UTC. temps est facultatif. |
print(os.date("%A %d %B à %H:%M")) -- « mardi 13 octobre à 07:30 »