Gamemodes

Un gamemode est le mode de jeu d'une partie, écrit en Lua : ses règles (armes, noclip, dégâts, menu de spawn), ses équipes, ses manches et son HUD. Sandbox est lui-même un gamemode.

Gamemodes et addons

GamemodeAddon Lua
RôleLes règles de la partieUn ajout chargé dans toutes les parties
Combien par partieUn seul, choisi en créant la partieTous les addons cochés dans Mes addons
Dossiergamemodes/<nom>/addons/<nom>/lua/ (voir Structure d'un addon)
Fonctionsfunction GM:PlayerSpawn(ply)…hook.Add("PlayerSpawn", "id", function(ply) … end)

Quand tu crées une partie (Jouer → Créer une partie), tu choisis d'abord le gamemode, puis la carte. Tous les addons activés sont chargés en plus.

Créer un gamemode

  1. Créer → Code → Nouveau gamemode : choisis un nom et le gamemode dont il dérive (Sandbox par défaut).
  2. L'éditeur de code s'ouvre sur les fichiers créés.
  3. TESTER (F5) lance une partie solo avec ton gamemode. F2 pour revenir au code, F5 pour recharger.

Pendant la partie, enregistrer un fichier du gamemode le ré-exécute seul (autorefresh) avec GM = la table du gamemode en cours : tes function GM:… remplacent les anciennes, sans redémarrer la partie, puis le hook OnReloaded est appelé.

Les gamemodes locaux sont dans %APPDATA%\Godot\app_userdata\PhysiCore\gamemodes\.

Structure

mon_gamemode/
├── gamemode.json     titre, description, gamemode de base
├── shared.lua        serveur + client, chargé en premier (équipes, constantes)
├── shared/*.lua      autres fichiers partagés
├── entities/*.lua    entités scriptées (une par fichier, classe = nom du fichier)
├── weapons/*.lua     armes Lua (SWEP, classe = nom du fichier), partagées
├── stools/*.lua      outils de toolgun Lua (TOOL), partagés
├── init.lua          serveur : les règles
├── server/*.lua      autres fichiers serveur
├── cl_init.lua       client : le HUD
└── client/*.lua      autres fichiers client

Les fichiers de chaque dossier sont chargés par ordre alphabétique, après le fichier principal. Il n'y a pas de include() : range simplement tes fichiers dans ces dossiers. Les armes et outils sont décrits dans Armes et outils en Lua, les points et zones de map dans Points, zones et entités de map.

{
	"title": "Chasse aux pièces",
	"description": "Deux équipes ramassent des pièces en 2 minutes.",
	"base": "sandbox",
	"author": "Moi"
}

Héritage

Un gamemode dérive d'un autre (clé base) : il reprend toutes ses fonctions et ne redéfinit que ce qu'il change. Les fichiers du gamemode de base sont chargés avant les siens. Pour appeler la version de base d'une fonction :

function GM:PlayerSpawn(ply)
	self.BaseClass.PlayerSpawn(self, ply) -- armes et noclip du gamemode de base
	ply:SetHealth(50)
end
Gamemode intégréComportement
Base (caché)Rien n'est autorisé : pas d'armes, pas de noclip, pas de menu de spawn. Idéal pour un mode de jeu strict.
Sandbox (dérive de Base)Toutes les armes (physgun, toolgun, pied-de-biche, pistolet, fusil à pompe, fusil d'assaut), noclip, menu de spawn avec l'onglet Armes. En partie solo, le physgun attrape aussi les PNJ.

Leur code se lit dans Créer → Code (lecture seule) ; Dupliquer en fait une copie modifiable.

DeriveGamemode

Comme dans GMod, la base peut aussi se déclarer en Lua. Si gamemode.json n'a pas de clé base, le jeu cherche DeriveGamemode("nom") en début de ligne dans shared.lua, puis init.lua, puis cl_init.lua, et s'en sert comme base (fichiers de la base chargés avant). Sans l'un ni l'autre, la base est Sandbox. Quand elle existe, la clé base de gamemode.json reste prioritaire.

-- shared.lua d'un gamemode porté de GMod (gamemode.json sans "base")
DeriveGamemode("base")

GM.Name = "Mon mode strict"

À l'exécution, DeriveGamemode(nom) fait hériter GM de ce gamemode (GM.BaseClass, et les fonctions absentes sont lues dans la base). Le gamemode doit déjà être chargé, c'est-à-dire être la base de la partie : sinon une erreur est écrite dans la console.

Quand tous les fichiers du gamemode et des addons ont été exécutés, le jeu appelle le hook PostGamemodeLoaded, puis GM:Initialize, dans chaque royaume.

Les fonctions du gamemode

Chaque événement appelle d'abord les hook.Add des addons, puis la fonction GM:Événement du gamemode (sauf si un hook a renvoyé une valeur). Les règles principales :

FonctionCôtéRôle
GM:PlayerLoadout(ply)serveurArmes de départ : ply:Give("physgun"), ply:Give("pistol")… (voir Armes).
GM:PlayerGiveWeapon(ply, arme)serveurtrue pour laisser prendre une arme dans le menu de spawn.
GM:PlayerSetModel(ply, id)serveurtrue pour accepter le playermodel qu'un joueur choisit dans Apparence ("" = mannequin). Base et Sandbox acceptent. Voir Playermodels.
GM:PlayerSpawnNPC(ply, classe)serveurtrue pour laisser faire apparaître un PNJ depuis le menu de spawn (Base : non, Sandbox : oui). Voir PNJ.
GM:PlayerNoClip(ply)serveurtrue pour autoriser le noclip.
GM:PlayerSpawnProp(ply, modèle)serveurfalse pour interdire le menu de spawn et les dupes.
GM:PlayerShouldTakeDamage(ply, cause, attaquant)serveurfalse pour annuler des dégâts (chutes, tirs…).
GM:PhysgunPickup(ply, ent)serveurfalse : le physgun ne l'attrape pas. ent est un prop, un véhicule ou un PNJ. Base refuse les PNJ ; Sandbox les accepte. En multijoueur, la protection des props a déjà refusé ce qui appartient aux autres.
GM.PropProtectionpartagéfalse (dans shared.lua) désactive la protection des props pour ce gamemode, qui gère alors lui-même ce que chacun peut toucher (hooks PhysgunPickup, CanTool…). Pour l'ajuster sans la couper : hook ProtectionCanTouch.
GM:HUDPaint()clientDessine ton HUD (voir Lua client).
GM:HUDShouldDraw(nom)clientfalse cache un élément du HUD du jeu.
GM:SpawnMenuOpen()clientfalse : la touche du menu de spawn ne fait rien.

La liste complète est dans Hooks et fonctions du gamemode.

Classes de joueur : player_manager

Une classe de joueur regroupe les réglages d'un rôle (vitesses, vie, armes). Déclare-la dans un fichier partagé avec player_manager.RegisterClass(nom, table, base) (base conseillée : "player_default", qui existe déjà), puis donne-la côté serveur avec player_manager.SetPlayerClass(ply, nom) : elle s'applique à la prochaine apparition.

Champ ou fonction de la classeRôle
DisplayNameNom affichable (le nom de la classe par défaut).
WalkSpeed, RunSpeed, SlowWalkSpeed, JumpPowerVitesses en m/s (comme ply:SetWalkSpeed… ; pas les unités de GMod).
MaxHealth, StartHealth, StartArmorVie maximale, vie et armure à l'apparition.
CLASS:Spawn()À chaque apparition, après les réglages ci-dessus.
CLASS:Loadout()Armes de départ (appelée par GM:PlayerLoadout de Base).

Dans ces fonctions, self.Player est le joueur. Le gamemode Base applique la classe dans GM:PlayerSpawn (player_manager.OnPlayerSpawn(ply)) et lance son Loadout dans GM:PlayerLoadout : un gamemode qui remplace ces fonctions doit les appeler lui-même (Sandbox remplace PlayerLoadout : le Loadout de la classe n'y est pas lancé). Aussi : player_manager.GetPlayerClass(ply) (des deux côtés, "" sans classe), player_manager.ClearPlayerClass(ply), player_manager.GetPlayerClasses(), et player_manager.RunClass(ply, "Fonction", …) pour appeler n'importe quelle fonction de sa classe.

-- shared.lua
local CLASS = {
	DisplayName = "Policier",
	WalkSpeed = 4,
	RunSpeed = 8,
	MaxHealth = 150,
	StartHealth = 150,
	StartArmor = 50,
}
function CLASS:Loadout()
	self.Player:Give("hands")
	self.Player:Give("pistol")
end
player_manager.RegisterClass("monmode_policier", CLASS, "player_default")

-- init.lua (le gamemode dérive de Base)
hook.Add("PlayerInitialSpawn", "monmode_classe", function(ply)
	player_manager.SetPlayerClass(ply, "monmode_policier")
end)

Équipes, points et manches

Exemple complet : chasse aux pièces

shared.lua

GM.Name = "Chasse aux pièces"

team.SetUp(1, "Rouges", Color(220, 60, 60))
team.SetUp(2, "Bleus", Color(60, 120, 230))

entities/piece.lua (voir Entités scriptées)

ENT.PrintName = "Pièce"
ENT.Model = "builtin:ball"
ENT.Physics = "static"
ENT.PhysgunDisabled = true

if SERVER then
	function ENT:StartTouch(ply)
		ply:AddFrags(1)
		team.AddScore(ply:Team(), 1)
		self:Remove()
	end
end

init.lua

local DUREE = 120

-- Équipe la moins nombreuse.
function GM:PlayerInitialSpawn(ply)
	if team.NumPlayers(1) <= team.NumPlayers(2) then
		ply:SetTeam(1)
	else
		ply:SetTeam(2)
	end
end

function GM:PlayerLoadout(ply) end                     -- pas d'armes
function GM:PlayerNoClip(ply) return false end
function GM:PlayerSpawnProp(ply, model) return false end

local function NouvelleManche()
	game.CleanUpMap()
	team.SetScore(1, 0)
	team.SetScore(2, 0)
	SetGlobalFloat("fin_manche", CurTime() + DUREE)
	for i = 1, 20 do
		local piece = ents.Create("piece")
		piece:SetPos(Vector(math.random(-40, 40), 1, math.random(-40, 40)))
		piece:Spawn()
	end
end

function GM:Initialize()
	timer.Simple(1, NouvelleManche)
end

function GM:Think(dt)
	local fin = GetGlobalFloat("fin_manche", 0)
	if fin > 0 and CurTime() >= fin then
		SetGlobalFloat("fin_manche", 0)
		local gagnant = team.GetScore(1) > team.GetScore(2) and 1 or 2
		PrintMessage("Les " .. team.GetName(gagnant) .. " gagnent la manche !")
		timer.Simple(5, NouvelleManche)
	end
end

cl_init.lua

function GM:HUDPaint()
	local reste = GetGlobalFloat("fin_manche", 0) - CurTime()
	if reste > 0 then
		draw.SimpleText(string.FormattedTime(reste), "Big", ScrW() / 2, 20, Color(255, 255, 255), TEXT_ALIGN_CENTER)
	end
	draw.SimpleText(team.GetName(1) .. " : " .. team.GetScore(1), "Bold", 20, 20, team.GetColor(1))
	draw.SimpleText(team.GetName(2) .. " : " .. team.GetScore(2), "Bold", 20, 44, team.GetColor(2))
end

function GM:HUDShouldDraw(name)
	return name ~= "Weapon" and name ~= "Crosshair"
end

Activer, partager