Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion INSTALL FIRST/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ CREATE TABLE IF NOT EXISTS `player_fractures` (
CREATE TABLE IF NOT EXISTS `medical_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`citizenid` varchar(50) NOT NULL,
`event_type` enum('wound_created', 'wound_change', 'treatment_applied', 'treatment_change', 'treatment_removed', 'infection_started', 'infection_cured', 'wound_healed', 'wound_scarred', 'fracture_created', 'fracture_healed', 'admin_clear_wounds', 'medical_inspection') NOT NULL,
`event_type` enum('wound_created', 'wound_change', 'treatment_applied', 'treatment_change', 'treatment_removed', 'infection_started', 'infection_cured', 'wound_healed', 'wound_scarred', 'fracture_created', 'fracture_healed', 'admin_clear_wounds', 'medical_inspection','medical_treatment') NOT NULL,
`body_part` varchar(20) DEFAULT NULL,
`details` json NOT NULL,
`performed_by` varchar(50) DEFAULT NULL,
Expand Down
12 changes: 9 additions & 3 deletions client/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,16 @@ end)
-- player update health loop
---------------------------------------------------------------------
CreateThread(function()
local lasthealth = 0
repeat Wait(1000) until LocalPlayer.state['isLoggedIn']
while true do
local health = GetEntityHealth(cache.ped)

-- PERFORMANCE FIX: Don't send server events when dead (saves network traffic)
if not deathactive then
if not deathactive and health ~= lasthealth then
TriggerServerEvent('QC-AdvancedMedic:server:SetHealth', health)
lasthealth = health
print('^2[QC-AdvancedMedic] Sent health update to server: ' .. tostring(health) .. '^7')
end

Wait(deathactive and 5000 or 1000) -- Check every 5 seconds when dead, every 1 second when alive
Expand Down Expand Up @@ -1492,9 +1495,11 @@ end)

-- Handle medical treatment messages from NUI (via window.postMessage)
RegisterNUICallback('medical-treatment', function(data, cb)
print(json.encode(data))
local action = data.action
local treatmentData = data.data

local targetPlayerId = treatmentData.playerId

if not action or not treatmentData then
cb({status = 'error', message = 'Invalid treatment data'})
return
Expand All @@ -1508,7 +1513,6 @@ RegisterNUICallback('medical-treatment', function(data, cb)
if action == 'administer-medicine' then
-- Use the existing administer-medicine callback logic
local medicineType = treatmentData.itemType
local targetPlayerId = treatmentData.playerId

if not medicineType then
cb({status = 'error', message = 'Missing medicine type'})
Expand Down Expand Up @@ -1550,6 +1554,7 @@ RegisterNUICallback('medical-treatment', function(data, cb)
end
else
-- Handle real player medicine application
print(string.format("^3[MEDICAL-TREATMENT] Administering %s to player %s^7", medicineType, tostring(targetPlayerId)))
TriggerServerEvent('QC-AdvancedMedic:server:MedicApplyMedicine', targetPlayerId, medicineType)
cb({status = 'success', message = 'Medicine administered successfully'})

Expand All @@ -1573,6 +1578,7 @@ RegisterNUICallback('medical-treatment', function(data, cb)

cb({status = 'success', message = 'Bandage applied to mission patient'})
else
print(string.format("^3[MEDICAL-TREATMENT] Applying %s bandage to player %s on body part %s^7", bandageType, tostring(targetPlayerId), bodyPart))
-- Handle real player bandage application
TriggerServerEvent('QC-AdvancedMedic:server:MedicApplyBandage', targetPlayerId, bodyPart, bandageType)
cb({status = 'success', message = 'Bandage applied successfully'})
Expand Down
38 changes: 24 additions & 14 deletions client/treatment_system.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ local InjectionEffects = {}
-- NEW BANDAGE SYSTEM - One-time Application with Decay
--=========================================================
function ApplyBandage(bodyPart, bandageType, appliedBy)
print("Applying bandage:", bodyPart, bandageType, appliedBy)

local bandageConfig = Config.BandageTypes[bandageType]
if not bandageConfig then
print("[ERROR] Unknown bandage type: " .. tostring(bandageType))
return false
end

local bodyPartConfig = Config.BodyParts[bodyPart]
local bodyPartConfig = Config.BodyParts[string.upper(bodyPart)] or Config.BodyParts[bodyPart]
if not bodyPartConfig then
print("[ERROR] Unknown body part: " .. tostring(bodyPart))
return false
Expand All @@ -45,7 +47,10 @@ function ApplyBandage(bodyPart, bandageType, appliedBy)
-- Get current wounds for this body part
local wounds = PlayerWounds or {}
local wound = wounds[bodyPart]
local bleadingLevel = wound and tonumber(wound.bleedingLevel) or 0
local painLevel = wound and tonumber(wound.painLevel) or 0

-- Check if wound exists
if not wound then
lib.notify({
title = locale('cl_menu_treatment'),
Expand All @@ -57,7 +62,8 @@ function ApplyBandage(bodyPart, bandageType, appliedBy)
end

-- Check if wound is bleeding (bandages only work on bleeding wounds)
if not wound.bleedingLevel or wound.bleedingLevel <= 0 then
print(bleadingLevel)
if not bleadingLevel or bleadingLevel <= 0 then
lib.notify({
title = locale('cl_menu_treatment'),
description = string.format(locale('cl_desc_fmt_no_bleeding_detected'), bodyPartConfig.label),
Expand Down Expand Up @@ -93,32 +99,32 @@ function ApplyBandage(bodyPart, bandageType, appliedBy)

-- SIMPLIFIED TIME-BASED BANDAGE SYSTEM
-- Store original wound levels for 50% return when bandage expires
local originalPain = wound.painLevel
local originalBleeding = wound.bleedingLevel
local originalPain = painLevel
local originalBleeding = bleadingLevel

-- BLEEDING REDUCTION (immediate effect)
local bleedingReduction = 0
if wound.bleedingLevel > 0 and bandageConfig.bleedingReduction then
if bleadingLevel > 0 and bandageConfig.bleedingReduction then
bleedingReduction = bandageConfig.bleedingReduction
-- Apply reduction but maintain minimum level 1 (wounds don't vanish)
wound.bleedingLevel = math.max(wound.bleedingLevel - bleedingReduction, 1)
bleadingLevel = math.max(bleadingLevel - bleedingReduction, 1)
end

-- PAIN REDUCTION (proportional to bleeding reduction since pain = bleeding + tissue damage)
local painReduction = 0
if bleedingReduction > 0 and wound.painLevel > 0 then
if bleedingReduction > 0 and painLevel > 0 then
-- Pain reduces proportionally to bleeding (since pain is related to bleeding)
-- But maintain minimum level 2 (tissue damage persists)
painReduction = bleedingReduction
wound.painLevel = math.max(wound.painLevel - painReduction, 2)
painLevel = math.max(painLevel - painReduction, 2)
end

-- Update wound data on server
TriggerServerEvent('QC-AdvancedMedic:server:UpdateWoundData', wounds)

if Config.WoundSystem.debugging.enabled then
print(string.format("^3[BANDAGE] Pain:%.1f→%.1f Bleed:%.1f→%.1f^7",
originalPain or 0, wound.painLevel or 0, originalBleeding or 0, wound.bleedingLevel or 0))
originalPain or 0, painLevel or 0, originalBleeding or 0, bleadingLevel or 0))
end

-- SIMPLIFIED TIME-BASED BANDAGE TRACKING
Expand Down Expand Up @@ -236,6 +242,7 @@ end
--=========================================================
local function ApplyTourniquet(bodyPart, tourniquetType, appliedBy)
local tourniquetConfig = Config.TourniquetTypes[tourniquetType]
local bodyPart = string.upper(bodyPart)
if not tourniquetConfig then
print("[ERROR] Unknown tourniquet type: " .. tostring(tourniquetType))
return false
Expand Down Expand Up @@ -282,11 +289,12 @@ local function ApplyTourniquet(bodyPart, tourniquetType, appliedBy)
-- Get current wounds for bleeding check
local wounds = PlayerWounds or {}
local wound = wounds[bodyPart]

local bleadingLevel = wound and tonumber(wound.bleedingLevel) or 0
local painLevel = wound and tonumber(wound.painLevel) or 0
-- Apply immediate bleeding control
if wound and wound.bleedingLevel > 0 then
if wound and bleadingLevel > 0 then
if math.random() <= tourniquetConfig.bleedingStopChance then
wound.bleedingLevel = 0
bleadingLevel = 0
TriggerServerEvent('QC-AdvancedMedic:server:UpdateWoundData', wounds)

lib.notify({
Expand Down Expand Up @@ -315,7 +323,7 @@ local function ApplyTourniquet(bodyPart, tourniquetType, appliedBy)

-- Increase pain due to tourniquet pressure
if wound and tourniquetConfig.painIncrease then
wound.painLevel = math.min(wound.painLevel + (tourniquetConfig.painIncrease / 10), 10)
painLevel = math.min(painLevel + (tourniquetConfig.painIncrease / 10), 10)
TriggerServerEvent('QC-AdvancedMedic:server:UpdateWoundData', wounds)
end

Expand Down Expand Up @@ -449,7 +457,8 @@ local function AdministreMedicine(medicineType, appliedBy)
-- Medicine affects pain conditions across all wounded body parts
if PlayerWounds then
for bodyPart, woundData in pairs(PlayerWounds) do
if woundData and woundData.painLevel and woundData.painLevel > 0 then
local painLevel = tonumber(woundData.painLevel) or 0
if woundData and painLevel and painLevel > 0 then
-- Add medicine treatment to the wound's treatments
if not woundData.treatments then
woundData.treatments = {}
Expand Down Expand Up @@ -694,6 +703,7 @@ end)
RegisterNetEvent('QC-AdvancedMedic:client:ApplyBandage')
AddEventHandler('QC-AdvancedMedic:client:ApplyBandage', function(bodyPart, bandageType, appliedBy)
ApplyBandage(bodyPart, bandageType, appliedBy)

end)

RegisterNetEvent('QC-AdvancedMedic:client:ApplyTourniquet')
Expand Down
4 changes: 2 additions & 2 deletions config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Config.MedicJobLocations = {
prompt = 'valmedic',
coords = vector3(-289.46, 806.82, 119.39 - 0.08),
showblip = true,
job = 'valmedic'
job = 'doctor'
},
{
name = 'Saint Denis',
Expand Down Expand Up @@ -318,7 +318,7 @@ Config.BodyParts = {
HEAD = { label = 'Head', maxHealth = 100 , limp = false },
NECK = { label = 'Neck', maxHealth = 80 , limp = false },
SPINE = { label = 'Spine', maxHealth = 120 , limp = true },
UPPER_BODY = { label = 'Upper Body', maxHealth = 150 , limp = false },
UPBODY = { label = 'Upper Body', maxHealth = 150 , limp = false },
LOWER_BODY = { label = 'Lower Body', maxHealth = 150 , limp = true },
LARM = { label = 'Left Arm', maxHealth = 90 , limp = false },
LHAND = { label = 'Left Hand', maxHealth = 60 , limp = false },
Expand Down
9 changes: 5 additions & 4 deletions server/database.lua
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ local function SaveTreatmentData(citizenid, treatmentData)
if not citizenid or not treatmentData then return false end

-- Mark all existing treatments as inactive
MySQL.Async.execute('UPDATE medical_treatments SET is_active = 0 WHERE citizenid = ? AND is_active = 1', {citizenid})

-- Insert current active treatments (supports all treatment types)
for bodyPart, treatment in pairs(treatmentData) do
Expand All @@ -229,9 +228,9 @@ local function SaveTreatmentData(citizenid, treatmentData)

MySQL.Async.execute([[
INSERT INTO medical_treatments
(citizenid, body_part, treatment_type, item_type, applied_by, expiration_time, duration,
(citizenid, body_part, treatment_type, item_type, applied_by, expiration_time, duration, is_active,
original_pain_level, original_bleeding_level, pain_reduction, bleeding_reduction, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
]], {
citizenid,
bodyPart,
Expand All @@ -240,6 +239,7 @@ local function SaveTreatmentData(citizenid, treatmentData)
treatment.appliedBy or citizenid, -- Default to self if not specified
mysqlExpirationTime, -- Properly formatted MySQL datetime
treatment.duration, -- NULL for bandages, seconds for medicines
1,
treatment.originalPainLevel, -- Original pain before treatment
treatment.originalBleedingLevel, -- Original bleeding before treatment
treatment.painReduction, -- How much pain was reduced
Expand All @@ -261,11 +261,12 @@ end

-- Load treatment data for a player
local function LoadTreatmentData(citizenid)
print('Loading treatment data for ' .. citizenid)
if not citizenid then return {} end

local result = MySQL.Sync.fetchAll('SELECT * FROM medical_treatments WHERE citizenid = ? AND is_active = 1', {citizenid})
local treatmentData = {}

print('Found ' .. #result .. ' treatment records')
for _, row in ipairs(result) do
local metadata = {}
if row.metadata then
Expand Down
27 changes: 15 additions & 12 deletions server/medical_server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ RegisterNetEvent('QC-AdvancedMedic:server:MedicApplyBandage')
AddEventHandler('QC-AdvancedMedic:server:MedicApplyBandage', function(targetId, bodyPart, bandageType)
local src = source
local Medic = RSGCore.Functions.GetPlayer(src)
local Patient = RSGCore.Functions.GetPlayer(targetId)

local Patient = RSGCore.Functions.GetPlayerByCitizenId(targetId)
print(targetId)
if not Medic or not Patient then return end

-- Check if source is a medic
Expand All @@ -216,7 +216,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyBandage', function(targetId,
end

-- Check if medic has the required item
if not Medic.Functions.GetItemByName(bandageType) then
if not Medic.Functions.GetItemByName(Config.BandageTypes[bandageType].itemName or bandageType) then
TriggerClientEvent('ox_lib:notify', src, {
title = locale('sv_missing_supplies'),
description = string.format(locale('sv_you_dont_have_item'), bandageType),
Expand All @@ -227,7 +227,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyBandage', function(targetId,
end

-- Remove item from medic
if Medic.Functions.RemoveItem(bandageType, 1) then
if Medic.Functions.RemoveItem(Config.BandageTypes[bandageType].itemName or bandageType, 1) then
TriggerClientEvent('rsg-inventory:client:ItemBox', src, RSGCore.Shared.Items[bandageType], 'remove', 1)

-- Apply treatment to patient
Expand All @@ -236,7 +236,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyBandage', function(targetId,
-- Log medical action
exports['QC-AdvancedMedic']:LogMedicalEvent(
Patient.PlayerData.citizenid,
'medic_treatment',
'medical_treatment',
string.format("Medic applied %s to %s", bandageType, bodyPart),
bodyPart,
Medic.PlayerData.citizenid
Expand All @@ -263,7 +263,8 @@ RegisterNetEvent('QC-AdvancedMedic:server:MedicApplyTourniquet')
AddEventHandler('QC-AdvancedMedic:server:MedicApplyTourniquet', function(targetId, bodyPart, tourniquetType)
local src = source
local Medic = RSGCore.Functions.GetPlayer(src)
local Patient = RSGCore.Functions.GetPlayer(targetId)
local Patient = RSGCore.Functions.GetPlayerByCitizenId(targetId)


if not Medic or not Patient then return end

Expand All @@ -279,7 +280,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyTourniquet', function(targetI
end

-- Check if medic has the required item
if not Medic.Functions.GetItemByName(tourniquetType) then
if not Medic.Functions.GetItemByName(Config.TourniquetTypes[tourniquetType].itemName or tourniquetType) then
TriggerClientEvent('ox_lib:notify', src, {
title = locale('sv_missing_supplies'),
description = string.format(locale('sv_you_dont_have_item'), tourniquetType),
Expand All @@ -290,7 +291,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyTourniquet', function(targetI
end

-- Remove item from medic
if Medic.Functions.RemoveItem(tourniquetType, 1) then
if Medic.Functions.RemoveItem(Config.TourniquetTypes[tourniquetType].itemName or tourniquetType, 1) then
TriggerClientEvent('rsg-inventory:client:ItemBox', src, RSGCore.Shared.Items[tourniquetType], 'remove', 1)

-- Apply emergency treatment to patient
Expand All @@ -299,7 +300,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyTourniquet', function(targetI
-- Log medical action
exports['QC-AdvancedMedic']:LogMedicalEvent(
Patient.PlayerData.citizenid,
'emergency_treatment',
'medical_treatment',
string.format("Medic applied emergency %s to %s", tourniquetType, bodyPart),
bodyPart,
Medic.PlayerData.citizenid
Expand All @@ -324,9 +325,11 @@ end)

RegisterNetEvent('QC-AdvancedMedic:server:MedicApplyMedicine')
AddEventHandler('QC-AdvancedMedic:server:MedicApplyMedicine', function(targetId, medicineType)
print("MedicApplyMedicine triggered",targetId)
local src = source
local Medic = RSGCore.Functions.GetPlayer(src)
local Patient = RSGCore.Functions.GetPlayer(targetId)
local Patient = RSGCore.Functions.GetPlayerByCitizenId(targetId)


if not Medic or not Patient then return end

Expand Down Expand Up @@ -384,7 +387,7 @@ AddEventHandler('QC-AdvancedMedic:server:MedicApplyMedicine', function(targetId,
-- Log medical action
exports['QC-AdvancedMedic']:LogMedicalEvent(
Patient.PlayerData.citizenid,
'medicine_treatment',
'medical_treatment',
string.format("Medic administered %s for pain management", medicineConfig.label or medicineType),
'patient', -- Medicine affects the whole patient, not specific body part
Medic.PlayerData.citizenid
Expand Down Expand Up @@ -556,7 +559,7 @@ end)
--=========================================================
-- MEDIC INSPECT COMMAND
--=========================================================
RSGCore.Commands.Add('inspect', 'Inspect another player\'s medical condition (Medic Only)', {{name = 'id', help = 'Player ID to inspect'}}, true, function(source, args)
RSGCore.Commands.Add('inspectmedic', 'Inspect another player\'s medical condition (Medic Only)', {{name = 'id', help = 'Player ID to inspect'}}, true, function(source, args)
local src = source
print('^3[QC-AdvancedMedic] DEBUG: /inspect command triggered by player ' .. src .. '^7')
local Medic = RSGCore.Functions.GetPlayer(src)
Expand Down