Parent : [[INDEX-FIVEM-GAMEPLAY]]
FiveM - Systeme medical avance : Anatomie, degats localises et soins progressifs
Probleme
Un systeme medical dans un serveur RP FiveM doit gerer : la localisation des degats (tete, bras, jambe, torse), les niveaux de blessure par arme, l'etat de conscience (mort/inconscient), le brancard/transport, et les soins progressifs. Le tout en server-authoritative pour eviter le godmode client-side.
Contexte
Basé sur les patterns extraits de wasabi-ambulance et vms_documents :
- Mapping os GTA → parties du corps (
31085 = head,45509 = left_arm, ...) - Table de degats par arme (
WEAPON_PISTOL = 1, ...) - Systeme UI de mort configurable (5 styles)
- Brancard avance avec offsets position/rotation
- Gestion d'etat medical (saignement, fracture, inconscience)
- Police peut traiter les blesses (configurable)
Solution
1. Mapping anatomique server-side
Anatomy = {
bones = {
[31085] = { part = 'head', critical = true, effect = 'instant_knockout' },
[45509] = { part = 'left_arm', critical = false, effect = 'aim_shake' },
[61163] = { part = 'right_arm', critical = false, effect = 'weapon_drop' },
[58271] = { part = 'left_leg', critical = false, effect = 'limp_slow' },
[63931] = { part = 'right_leg', critical = false, effect = 'limp_slow' },
[51826] = { part = 'torso', critical = true, effect = 'bleeding_heavy' },
[36864] = { part = 'pelvis', critical = false, effect = 'limp_slow' },
},
damageLevels = {
[1] = { label = 'Leger', bleedRate = 0.1, recovery = 30 },
[2] = { label = 'Modere', bleedRate = 0.3, recovery = 60 },
[3] = { label = 'Grave', bleedRate = 0.6, recovery = 120 },
[4] = { label = 'Critique', bleedRate = 1.0, recovery = 300 },
},
}
2. Degats par arme (server-side)
WeaponDamageTable = {
[`WEAPON_PISTOL`] = { base = 2, headMultiplier = 4.0, limbMultiplier = 0.5 },
[`WEAPON_CARBINERIFLE`] = { base = 3, headMultiplier = 5.0, limbMultiplier = 0.7 },
[`WEAPON_KNIFE`] = { base = 3, headMultiplier = 2.0, limbMultiplier = 0.8, bleedBonus = 0.5 },
}
RegisterNetEvent('medical:registerDamage')
AddEventHandler('medical:registerDamage', function(weaponHash, boneId, damageReported)
local src = source
local weaponData = WeaponDamageTable[weaponHash] or { base = 1, headMultiplier = 2.0, limbMultiplier = 0.5 }
local boneData = Anatomy.bones[boneId] or { part = 'torso', critical = false }
local multiplier = boneData.critical and weaponData.headMultiplier or weaponData.limbMultiplier
local finalDamage = weaponData.base * multiplier
local bleedRate = Anatomy.damageLevels[math.min(finalDamage, 4)].bleedRate
local currentState = Player(src).state.medicalState or { injuries = {}, blood = 100 }
table.insert(currentState.injuries, {
part = boneData.part,
level = math.min(finalDamage, 4),
bleedRate = bleedRate,
time = os.time()
})
currentState.blood = math.max(currentState.blood - (bleedRate * 10), 0)
Player(src).state:set('medicalState', currentState, true)
end)
3. Saignement progressif (server-side tick)
Citizen.CreateThread(function()
while true do
Citizen.Wait(1000)
for _, playerId in ipairs(GetPlayers()) do
local medicalState = Player(playerId).state.medicalState
if medicalState and medicalState.injuries then
local totalBleed = 0
for _, injury in ipairs(medicalState.injuries) do
totalBleed = totalBleed + injury.bleedRate
end
if totalBleed > 0 then
medicalState.blood = math.max(medicalState.blood - totalBleed, 0)
Player(playerId).state:set('medicalState', medicalState, true)
if medicalState.blood <= 0 then
Player(playerId).state:set('isDead', true, true)
TriggerClientEvent('medical:onDeath', playerId)
end
end
end
end
end
end)
4. Brancard avec physique server-side
local Stretchers = {}
function Stretcher:create(stretcherId, ownerId)
local ownerPed = GetPlayerPed(ownerId)
local ownerPos = GetEntityCoords(ownerPed)
self[stretcherId] = {
owner = ownerId,
position = ownerPos,
attachedPatient = nil,
offsets = Config.StretcherOffsets or {
attach = vector3(0.0, 0.0, 0.95),
carry = vector3(0.0, -0.5, 0.0),
}
}
TriggerClientEvent('stretcher:spawned', -1, stretcherId, self[stretcherId])
end
RegisterNetEvent('stretcher:updatePosition')
AddEventHandler('stretcher:updatePosition', function(stretcherId, pos, rot)
local src = source
local stretcher = Stretchers[stretcherId]
if not stretcher or stretcher.owner ~= src then return end
local maxMove = 10.0
if #(pos - stretcher.position) > maxMove then return end
stretcher.position = pos
stretcher.rotation = rot
TriggerClientEvent('stretcher:syncPosition', -1, stretcherId, pos, rot)
end)
5. Soins progressifs avec timer + distance check
function MedicalSystem:startTreatment(medicId, patientId, treatmentType)
if not self:canTreat(medicId, patientId) then return false end
local treatmentConfig = Config.Treatments[treatmentType]
if not treatmentConfig then return false, 'Traitement inconnu' end
TriggerClientEvent('medical:startProgress', medicId, {
duration = treatmentConfig.duration,
label = ('Soigner %s (%s)'):format(treatmentConfig.targetPart, treatmentConfig.label),
canCancel = true,
})
SetTimeout(treatmentConfig.duration, function()
local medicPed = GetPlayerPed(medicId)
local patientPed = GetPlayerPed(patientId)
if not medicPed or not patientPed then
TriggerClientEvent('medical:treatmentFailed', medicId, 'Joueur deconnecte')
return
end
if #(GetEntityCoords(medicPed) - GetEntityCoords(patientPed)) > 3.0 then
TriggerClientEvent('medical:treatmentFailed', medicId, 'Patient trop loin')
return
end
self:applyTreatment(patientId, treatmentType)
TriggerClientEvent('medical:treatmentSuccess', medicId, treatmentType)
end)
return true
end
6. Police peut traiter (configurable)
Config.CanPoliceTreat = true
Config.PoliceTreatmentLevel = 2
function MedicalSystem:canTreat(medicId, patientId)
local medicJob = self:getPlayerJob(medicId)
if medicJob == 'ambulance' then return true end
if medicJob == 'police' and Config.CanPoliceTreat then return true end
if medicId == patientId then
local state = Player(medicId).state.medicalState
if state then
local maxInjury = 0
for _, injury in ipairs(state.injuries) do
maxInjury = math.max(maxInjury, injury.level)
end
return maxInjury <= 1
end
end
return false
end
Checklist de validation
- Mapping anatomique server-side (pas de trust du client)
- Degats par arme calcules server-side
- Saignement progressif gere par un tick serveur
- Knockout/mort decides par le serveur
- Brancard avec physique server-side (position validee)
- Soins avec timer + distance check server-side
- Police peut traiter configurable
- Progress bar/action configurable (duree, animation, cancellable)
- Logs d'audit pour soins et morts
- Pas de godmode possible cote client
- Recuperation naturelle progressive (configurable)
References
- Patterns source :
wasabi-ambulance,vms_documents - FiveM Native :
GetEntityCoords,GetPedLastDamageBone,GetWeaponDamage