v1.0.0

Summary

This is the 1.0.0 tag of Luxu Medicine. EMS can train and certify civilians (First Aid / Stop the Bleed), hang blood bags on a placed IV stand, cut clothing with reusable trauma shears, and load stretchers into polmav / maverick. Downed players stay engine-alive in a dead pose so carry and stretchers still work; they hold G (configurable) to send distress instead of an automatic call. Hospital respawn waits Config.status.dead.respawn_min_wait (default 5 minutes) even after instant death.

This is not a wipe-and-replace install like v0.0.5, but merge config/config.lua and add the new items (scissors, iv_stand) from INSTALL/items. Keep Config.database.auto_install on (or run INSTALL/main.sql) so luxu_medicine_player_certifications and luxu_medicine_blood_donations.paid_at exist. Replace luxu_medicine_addons as well — bandage, shears, IV stand/bag, syringe, and CPR dummy props live there. If you copy the shipped config wholesale, note external_tablets.disable_dispatch now defaults to false.

What's New

Features

  • Player formation (Config.features.player_formation): bosses/trainers at trainer_minimum_job_grade run First Aid (CPR dummy luxu_prop_cpr_dummy) and Stop the Bleed (freemode dummy with cut clothing + dressings). Trainees consent, pay a suggested price, and receive a certification with optional expiry (luxu_medicine_player_certifications)
  • IV stand item: place luxu_prop_iv_stand, then hang luxu_prop_iv_bag_blood for transfusions (Config.items.iv_stand, PropsConfig.iv)
  • Trauma shears (scissors): never consumed; drag onto a region to cut clothing (or remove a dressing). Bandage overlay is grab-the-roll and drag onto a bleeding wound; dressings last dressing_minutes
  • Helicopter stretcher: load/unload into polmav and maverick using PropsConfig.heli_stretcher (m23_2_prop_m32_lgstretcher_01a)
  • Deathscreen distress: hold Config.distress.control (default 47 / G) for hold_duration ms. hooks.sendDistressSignal now runs on that hold, not automatically on down
  • Dead peds stay engine-alive (MIN_HEALTH + 1) in the dead pose so carry, stretcher, and treatment keep working
  • Config.status.dead.respawn_min_wait gates hospital E-respawn from the moment the player goes down
  • CPR cycles run Config.resuscitation.cpr_skill_checks (default easy / medium / medium); a miss ends the cycle without ROSC
  • Blood donation cooldown (Config.blood.donation.cooldown_days, default 7) and a management Pay action for donations that were unpaid when society funds were low (paid_at)
  • Optional Config.connect_url (HTTPS join URL) for garage vehicle images; garage grid/image sizing adjusted
  • Dispatch vehicle panel: ground vs air (helicopters listed in Config.job.job_vehicles), filters, driver, moving/parked
  • okokPhone EMS app moved to shared/phone_app.lua (outbreak map + medic availability); re-registers if okokPhone restarts
  • Deathscreen burn-in / impact-flash options in the HUD editor

Bug Fixes

  • Stretcher no longer IK-reaches for vehicle doors while being pushed
  • Helicopter stretcher attach offsets/renames
  • qbx_vehiclekeys GiveKeys receives the vehicle entity; garage key grant is pcall-wrapped
  • Garage vehicle images no longer crop as tightly in the tablet
  • Dispatcher animation
  • Training dummy placement

File Changes

⚙️ Configuration & Bridge Files

These files are not encrypted. Review the changes below — you may need to manually apply updates to your server's config files.

config/config.lua

     debug = true,
     dev_mode = true,  -- This is used for our development mode, might have issues, don't use it.

+    connect_url = "", -- [OPTIONAL] Your server's domain name that let's you join FiveM e.g. https://play.mycommunityrp.com (MUST BE HTTPS)
     external_tablets = {
         enabled = true,          --If true, lb-tablet app will be loaded immediately.
-        disable_dispatch = true -- Disables the small dispatch menu, uses tablet notifications instead.
+        disable_dispatch = false -- Disables the small dispatch menu, uses tablet notifications instead.
     },
     features = {
         --- Outbreaks, infections (zones, rain, food, player spread), and the tablet Diseases page.
         --- Legacy master switch; Config.diseases.enabled=false also forces this off (see shared/init.lua).
         diseases = true,
+
+        --- EMS staff can train and certify normal players
+        --- Allowing them to perform some basic first aid actions
+        player_formation = {
+            enabled = true,
+            trainer_minimum_job_grade = 1,
+            consent_timeout = 15000,  -- ms the patient has to accept/deny a training request
+            session_timeout = 600000, -- ms before an idle training session is cancelled
+            courses_enabled = {
+                first_aid = {
+                    enabled = true,
+                    task = "cpr",
+                    icon = "stabilize",
+                    suggested_price = 500,
+                    valid_until = 30,      -- days; -1 = no expiration
+                },
+                stop_bleed = {
+                    enabled = true,
+                    task = "stop_bleed",
+                    icon = "bandage",
+                    suggested_price = 500,
+                    valid_until = -1,
+                },
+            },
+            tasks = {
+                cpr = {
+                    dummy_model = "luxu_prop_cpr_dummy",
+                    attempts = 3,
+                    failures_per_attempt = 2,
+                    difficulties = { "easy", "medium", "hard" },
+                    attempt_duration_ms = 5000,
+                },
+                stop_bleed = {
+                    ped_model = "mp_m_freemode_01",
+                    sites = { "torso", "left_arm", "right_leg" },
+                },
+            },
+        }
     },
-        job_vehicles = { "ambulance", }, -- A list of vehicle models related to the job
+        job_vehicles = { "ambulance", }, -- A list of vehicle models related to the job. Helicopters listed here show as air units on dispatch
             { name = "stretcher", label = "Stretcher", category = "Equipment", unitPrice = 150, reorderAt = 4 },
+            { name = "iv_stand", label = "IV Stand", category = "Equipment", unitPrice = 90, reorderAt = 4 },
             { name = "paracetamol", label = "Paracetamol", category = "Medication", unitPrice = 45, reorderAt = 20 },
+    distress = {
+        hold_duration = 3000,
+        control = 47, -- G
+    },
+
     air_support = {
         enabled = true,
         hold_duration = 3000,
             consent_timeout = 15000, -- ms the patient has to accept/deny
             duration = 15000,        -- ms; non-cancelable extraction progress bar
+            cooldown_days = 7,       -- days between donations per patient (0.5 = 12h); 0 disables
         },
         dead = {
+            -- ms after going down (last standing or dead) before hospital respawn unlocks. Applies to instant deaths too.
+            respawn_min_wait = 5 * 60 * 1000,
             -- Hospital E-respawn inventory wipe. false keeps everything and ignores filtered_items.
             clear_inventory = true,
         cpr_success_chance = 0.10,
         cpr_duration_ms = 12000,
+        cpr_skill_checks = { "easy", "medium", "medium" }, -- one ox_lib skill check per entry, spread across the cycle. A miss ends the cycle without ROSC.
         rosc_health = 150,
         bandage = "bandage",
+        scissors = "scissors", -- trauma shears, never consumed
         thermometer = "thermometer",
         medical_kit = "medical_kit",
+        iv_stand = "iv_stand", -- placed, then blood bags are hung on it for transfusions

bandage_interaction is no longer hold / skill-key / apply timings. It is wrap speed, reach, dressing lifetime, and a hand-prop (luxu_prop_bandage_roll). New sibling scissors_interaction configures trauma shears reach, cut time, and which clothing slots each overlay region cuts.

config/hooks/client.lua

---- Called before an automatic distress signal is sent (last standing or dead).
+--- Called when a downed player holds the deathscreen key to send a distress signal.
+--- Return false to skip the built-in dispatch call (e.g. send it to your own dispatch resource instead).
 ---@return boolean
 function hooks.sendDistressSignal()
     return true
 end

config/props.lua

+    heli_stretcher = {
+        model = `m23_2_prop_m32_lgstretcher_01a`,
+        mount = { x = 0.0, y = 0.0, z = 1.00, rotx = 0.0, roty = 0.0, rotz = 0.0 },
+        vehicles = {
+            [`polmav`] = { x = 0.0, y = 0.95, z = -0.65, rotx = 0.0, roty = 0.0, rotz = 90.0 },
+            [`maverick`] = { x = 0.0, y = 0.2, z = -0.15, rotx = 0.0, roty = 0.0, rotz = 90.0 },
+        },
+    },
     iv = {
-        model = `luxu_medicine_blood_iv`,
+        model = `luxu_prop_iv_stand`,
+        bag_model = `luxu_prop_iv_bag_blood`,
         target_distance = 3.0,
-        start_distance = 5.0,
+        start_distance = 2.5, -- max stand-to-patient distance, the IV tube only reaches so far
     },

fxmanifest.lua

 description "Luxu Medicine - Ambulance Job"
-version "0.0.6"
+version "1.0.0"
     "server/modules/**/*.lua",
     "server/features/**/*.lua",
-    "server/phone_app.lua",
     "server/sv_main.lua"
     "config/hooks/client.lua",
     "locales/*.json",
-    "INSTALL/main.sql",
-    "INSTALL/patches.sql",
     "data/*.json",

Config.database.auto_install still loads those SQL files via LoadResourceFile. The schema runner also ALTERs in paid_at and the certifications trainer column when the table already exists.

INSTALL/main.sql

   `reward_amount` int(11) NOT NULL DEFAULT 0,
   `paid` tinyint(1) NOT NULL DEFAULT 0,
+  `paid_at` datetime DEFAULT NULL,
   `created_at` datetime NOT NULL DEFAULT current_timestamp(),
+CREATE TABLE IF NOT EXISTS `luxu_medicine_player_certifications` (
+  `char_id` varchar(255) NOT NULL,
+  `certification` varchar(255) NOT NULL,
+  `trainer` varchar(255) NOT NULL,
+  `expires_at` datetime NOT NULL,
+  KEY `idx_char_id` (`char_id`),
+  UNIQUE KEY `character_certification` (`char_id`,`certification`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;

INSTALL/items

Add scissors (Trauma Shears, reusable) and iv_stand (IV Stand) plus their images. Copy into ox_inventory / qb-inventory as usual.


Full list of files changed

Change File
Added INSTALL/items/images/iv_stand.png
Added INSTALL/items/images/scissors.png
Modified INSTALL/items/ox_inventory.lua
Modified INSTALL/items/qb_inventory.lua
Modified INSTALL/main.sql
Modified bridge/client/player.lua
Modified bridge/server/garage.lua
Modified bundle/luxu_medicine_addons/
Modified client/handlers/dev_commands.lua
Modified client/handlers/nui.lua
Modified client/lib/clothing.lua
Modified client/modules/formation/
Added client/modules/player_formation.lua
Modified client/modules/minigames.lua
Modified client/modules/props.lua
Modified client/modules/status.lua
Added client/modules/treatment/bandage.lua
Modified client/modules/treatment/blood_transfusion.lua
Modified client/modules/treatment/cpr.lua
Added client/modules/treatment/hand_tools/
Modified client/modules/treatment/patient_hotbar.lua
Modified client/modules/treatment/stretcher.lua
Modified client/modules/treatment/triage.lua
Modified config/config.lua
Modified config/hooks/client.lua
Modified config/props.lua
Modified fxmanifest.lua
Modified locales/en.json
Modified locales/es.json
Modified locales/pt.json
Modified server/database_patches.lua
Modified server/features/blood_donations.lua
Modified server/features/dispatch.lua
Added server/features/player_formation.lua
Modified server/modules/blood_transfusion.lua
Modified server/modules/garage.lua
Modified server/modules/job.lua
Modified server/modules/map.lua
Modified server/modules/minigames.lua
Removed server/phone_app.lua
Added shared/phone_app.lua
Modified server/sv_status.lua
Modified shared/init.lua
Modified web/