-
Notifications
You must be signed in to change notification settings - Fork 9
Server Systems
Complete technical reference for all server-side systems: database operations, event handling, medical profiles, commands, and caching.
File: server/database.lua (713 lines)
- Handles all SQL operations
- Manages data persistence for 5 tables
- Logs medical history events
- Provides stored procedures for optimization
-
player_wounds- Active wounds and scars -
medical_treatments- Current active treatments -
player_infections- Active infections -
player_fractures- Bone fractures/breaks -
medical_history- Complete audit trail
See Database Schema for complete table structure.
-- Save wound data (UPSERT query)
exports['QC-AdvancedMedic']:SaveWoundData(citizenid, woundData)
-- Load complete medical profile
local profile = exports['QC-AdvancedMedic']:GetCompleteMedicalProfile(citizenid)
-- Returns: {wounds, treatments, infections, fractures, scars, history}
-- Log medical event
exports['QC-AdvancedMedic']:LogMedicalEvent(citizenid, eventType, bodyPart, details, performedBy)-
wound_created- New injury occurred -
wound_scarred- Wound healed to scar -
treatment_applied- Bandage/medicine applied -
treatment_removed- Treatment expired -
infection_started- Infection began -
infection_cured- Infection cured -
medical_inspection- Medic examined player -
admin_clear_wounds- Admin cleared all wounds
Note
Medical history is automatically cleaned up after 30 days to prevent database bloat. Use the CleanupExpiredMedicalData() stored procedure.
File: server/medical_events.lua (684 lines)
- Handles 40+ network events between client/server
- Validates medic permissions
- Syncs medical data in real-time
- Broadcasts updates to nearby players
-
UpdateWoundData- Client sends wound changes -
UpdateTreatmentData- Client sends treatment changes -
UpdateInfectionData- Client sends infection changes -
LoadMedicalData- Client requests data on spawn
-
ApplyTreatment- Medic treats patient -
TreatInfection- Medic applies cure -
StartMedicalInspection- Medic inspects patient -
CompleteMedicalInspection- Medic finishes inspection
When wounds change, nearby medics (within 10m) are notified automatically:
-- Find nearby medics
for _, player in pairs(RSGCore.Functions.GetPlayers()) do
local targetCoords = GetEntityCoords(GetPlayerPed(player))
if #(coords - targetCoords) < 10.0 then
if IsMedicJob(RSGCore.Functions.GetPlayer(player).PlayerData.job.name) then
TriggerClientEvent('QC-AdvancedMedic:client:UpdateNearbyPlayerWounds', player, source, woundData)
end
end
endImportant
All treatment applications require job validation via IsMedicJob() to prevent non-medics from treating others.
File: server/medical_server.lua (929 lines)
- Maintains server-side cache of player medical data
- Handles
/inspectcommand for medics - Manages automatic wound progression
- Processes natural pain healing
PlayerMedicalData[playerId] = {
citizenid = "ABC12345",
wounds = {...},
treatments = {...},
infections = {...},
bandages = {...},
lastSync = os.time() -- Last database sync time
}Cache Management:
- Initialized on player connect
- Updated on every medical event
- Synced to database every 5 minutes
- Saved on player disconnect
Command: /inspect [player_id]
Permission: Medic jobs only (validated via IsMedicJob())
Distance Limit: 10 meters
Flow:
- Medic executes
/inspect 5 - Server validates:
- Medic has valid job
- Target player exists
- Distance is within 10m
- Server fetches medical profile from cache (or database if cache empty)
- Server sends data to medic client
- NUI opens showing complete medical status
- Inspection is logged to
medical_historytable
Data Sent to Medic:
{
playerName = "John Doe",
playerId = "ABC12345",
playerSource = 5,
wounds = {
["HEAD"] = {painLevel = 5, bleedingLevel = 3, metadata = {...}},
["LARM"] = {painLevel = 2, bleedingLevel = 1, metadata = {...}}
},
treatments = {
["HEAD"] = {treatmentType = "bandage", itemType = "cotton", appliedTime = 1234567890}
},
infections = {
["LARM"] = {stage = 2, startTime = 1234567000}
},
inspectedBy = "XYZ67890",
inspectionTime = 1700000000
}Tip
Medics can see ALL medical data including hidden infections and exact wound severity, not just visible symptoms.
Bleeding Progression (every 1 minute):
- Untreated wounds: 15% chance to increase bleeding by 1 level
- Only affects wounds without active bandage
- Caps at bleeding level 10 (max)
Natural Pain Healing (every 5 minutes):
- All wounds: Pain decreases by 1 level
- Simulates natural recovery over time
- Only affects pain, not bleeding
Note
Server-side progression is SEPARATE from client-side progression. Both run in parallel for redundancy.
File: server/server.lua (549 lines)
- Registers all admin/medic commands
- Creates useable items dynamically from config
- Manages duty pay system (10-minute intervals)
- Handles pharmaceutical shop purchases
| Command | Permission | Description |
|---|---|---|
/revive [id] |
admin |
Revive player (self if no ID) |
/heal [id] |
admin |
Heal player to full HP |
/kill [id] |
admin |
Kill target player |
/clearwounds [id] |
admin |
Clear all wounds, treatments, infections, fractures |
/medwounds [id] |
admin |
View player medical data count |
| Command | Permission | Description |
|---|---|---|
/inspect [id] |
Medic jobs | Inspect patient medical condition |
Items are created automatically from Config:
-- Bandages (from Config.BandageTypes)
for bandageType, config in pairs(Config.BandageTypes) do
RSGCore.Functions.CreateUseableItem(config.itemName, function(source, item)
TriggerClientEvent('QC-AdvancedMedic:client:usebandage', source, bandageType)
end)
endItems Created:
- Bandages: cloth_band, cotton_band, linen_band, sterile_band
- Cure Items: antibiotics, alcohol, cocaine (from InfectionSystem.cureItems)
- Medicines: medicine_laudanum, medicine_morphine, medicine_whiskey, medicine_quinine
- Medical Bag: medicalbag
How It Works:
- Medic goes on duty
- Client triggers
StartDutyPayTimerevent - Server pays every 10 minutes
- On duty end, partial pay calculated for remaining minutes
Pay Rates by Grade:
| Grade | Title | Pay Rate |
|---|---|---|
| 0 | Recruit | $1/hour |
| 1 | Trainee | $2/hour |
| 2 | Pharmacist | $3/hour |
| 3 | Doctor | $4/hour |
| 4 | Surgeon | $6/hour |
| 5 | Manager/Boss | $8/hour |
Payment Calculation:
-- Every 10 minutes
local payAmount = math.floor((hourlyRate * 10) / 60)
-- Partial pay (< 10 minutes)
if remainingMinutes >= 5 then
local partialPay = math.floor((hourlyRate * remainingMinutes) / 60)
endNote
Duty pay is automatically stopped on disconnect. Timers are cleaned up to prevent memory leaks.
Reduce database queries by ~90% through in-memory caching.
PlayerMedicalData = {
[playerId] = {
citizenid = "ABC12345",
wounds = PlayerWounds, -- Copy of client wounds
treatments = ActiveTreatments, -- Copy of client treatments
infections = PlayerInfections, -- Copy of client infections
bandages = BandageTracker, -- Legacy bandage tracking
lastSync = os.time() -- Last database sync time
}
}-
Initialization: When player connects
RegisterNetEvent('RSGCore:Server:OnPlayerLoaded', function() local src = source LoadPlayerMedicalDataToCache(src) end)
-
Updates: On every medical event
RegisterNetEvent('QC-AdvancedMedic:server:UpdateWoundData', function(woundData) PlayerMedicalData[source].wounds = woundData PlayerMedicalData[source].lastSync = os.time() end)
-
Periodic Sync: Every 5 minutes to database
CreateThread(function() while true do Wait(300000) -- 5 minutes SyncAllPlayerDataToDatabase() end end)
-
Cleanup: On player disconnect
RegisterNetEvent('RSGCore:Server:OnPlayerUnload', function() local src = source SavePlayerMedicalData(src) -- Final save PlayerMedicalData[src] = nil -- Clear cache end)
Without Cache:
- Every wound update = 1-2 database queries
- Every treatment = 1-2 database queries
- Every inspection = 5-10 database queries
- ~50 queries/minute per active player
With Cache:
- Most operations = 0 database queries (cached)
- Periodic sync = 1 batch query per player
- ~5 queries/minute per active player
Result: 90% reduction in database load
Used throughout the system to validate medic permissions:
function IsMedicJob(jobName)
local medicJobs = {
'doctor',
'medic',
'surgeon',
-- Add your medic jobs here
}
for _, job in ipairs(medicJobs) do
if job == jobName then
return true
end
end
return false
endUsed For:
-
/inspectcommand permission - Treatment application validation
- Nearby medic broadcasting
- Duty pay eligibility
v0.3.1-alpha