diff --git a/vrp/User.lua b/vrp/User.lua
index e0dbab8..d5a0b2f 100644
--- a/vrp/User.lua
+++ b/vrp/User.lua
@@ -42,7 +42,7 @@ end
function User:save()
vRP:setUData(self.id, "vRP:datatable", msgpack.pack(self.data))
- if not self.loading_character then
+ if not self.loading_character and self.cid then
vRP:setCData(self.cid, "vRP:datatable", msgpack.pack(self.cdata))
end
end
@@ -63,13 +63,51 @@ end
function User:createCharacter()
local characters = self:getCharacters()
if #characters < vRP.cfg.max_characters then
- local rows = vRP:query("vRP/create_character", {user_id = self.id})
- if #rows > 0 then
- return rows[1].id
+ local ok = vRP:execute("vRP/create_character", {user_id = self.id})
+ if ok then
+ Citizen.Wait(100) -- Small delay to ensure the insert is processed
+ local rows = vRP:query("vRP/get_last_character_id", {})
+ if #rows > 0 then
+ local character_id = rows[1].id
+ if character_id and character_id > 0 then
+ -- Initialize character data for new character
+ self:_initCharacterData(character_id)
+ return character_id
+ end
+ end
end
end
end
+-- initialize character data
+function User:_initCharacterData(character_id)
+ -- Initialize cdata with default values for all modules
+ self.cdata = {
+ -- Player state data
+ state = {
+ position = nil,
+ heading = nil,
+ health = nil,
+ weapons = nil,
+ components = nil,
+ customization = nil
+ },
+ -- Group data
+ groups = {},
+ -- HUD settings
+ hud_settings = {},
+ hud_enabled = true,
+ hud_show_health = true,
+ hud_show_armor = true,
+ hud_show_hunger = true,
+ hud_show_thirst = true,
+ hud_show_stamina = false
+ }
+
+ -- Save initial character data
+ vRP:setCData(character_id, "vRP:datatable", msgpack.pack(self.cdata))
+end
+
-- use character
-- return true or false, err_code
-- Removed delay for character switching to improve responsiveness.
@@ -107,6 +145,9 @@ function User:useCharacter(id, bypass)
local sdata = vRP:getCData(self.cid, "vRP:datatable")
if sdata and string.len(sdata) > 0 then
self.cdata = msgpack.unpack(sdata)
+ else
+ -- Initialize with default values for new or empty character data
+ self:_initCharacterData(self.cid)
end
vRP:triggerEventSync("characterLoad", self)
diff --git a/vrp/base.lua b/vrp/base.lua
index c335355..db395fe 100644
--- a/vrp/base.lua
+++ b/vrp/base.lua
@@ -17,65 +17,26 @@ pvRP.loadScript = module
Proxy.addInterface("vRP", pvRP)
-- queries
-vRP:prepare("vRP/base_tables",[[
-CREATE TABLE IF NOT EXISTS vrp_users(
- id INTEGER AUTO_INCREMENT,
- CONSTRAINT pk_user PRIMARY KEY(id)
-);
-
-CREATE TABLE IF NOT EXISTS vrp_user_ids(
- identifier VARCHAR(100),
- user_id INTEGER,
- CONSTRAINT pk_user_ids PRIMARY KEY(identifier),
- CONSTRAINT fk_user_ids_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE
-);
-
-CREATE TABLE IF NOT EXISTS vrp_characters(
- id INTEGER AUTO_INCREMENT,
- user_id INTEGER,
- CONSTRAINT pk_characters PRIMARY KEY(id),
- CONSTRAINT fk_characters_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE
-);
-
-CREATE TABLE IF NOT EXISTS vrp_user_data(
- user_id INTEGER,
- dkey VARCHAR(100),
- dvalue BLOB,
- CONSTRAINT pk_user_data PRIMARY KEY(user_id,dkey),
- CONSTRAINT fk_user_data_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE
-);
-
-CREATE TABLE IF NOT EXISTS vrp_character_data(
- character_id INTEGER,
- dkey VARCHAR(100),
- dvalue BLOB,
- CONSTRAINT pk_character_data PRIMARY KEY(character_id,dkey),
- CONSTRAINT fk_character_data_characters FOREIGN KEY(character_id) REFERENCES vrp_characters(id) ON DELETE CASCADE
-);
-
-CREATE TABLE IF NOT EXISTS vrp_server_data(
- id VARCHAR(100),
- dkey VARCHAR(100),
- dvalue BLOB,
- CONSTRAINT pk_server_data PRIMARY KEY(id, dkey)
-);
-
-CREATE TABLE IF NOT EXISTS vrp_global_data(
- dkey VARCHAR(100),
- dvalue BLOB,
- CONSTRAINT pk_global_data PRIMARY KEY(dkey)
-);
-]])
-
-vRP:prepare("vRP/create_user","INSERT INTO vrp_users(id) VALUES(DEFAULT); SELECT LAST_INSERT_ID() AS id")
-
-vRP:prepare("vRP/create_character", "INSERT INTO vrp_characters(user_id) VALUES(@user_id); SELECT LAST_INSERT_ID() AS id")
+vRP:prepare("vRP/base_tables","CREATE TABLE IF NOT EXISTS vrp_users(id INTEGER AUTO_INCREMENT, CONSTRAINT pk_user PRIMARY KEY(id))")
+vRP:prepare("vRP/base_tables2","CREATE TABLE IF NOT EXISTS vrp_user_ids(identifier VARCHAR(100), user_id INTEGER, CONSTRAINT pk_user_ids PRIMARY KEY(identifier), CONSTRAINT fk_user_ids_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE)")
+vRP:prepare("vRP/base_tables3","CREATE TABLE IF NOT EXISTS vrp_characters(id INTEGER AUTO_INCREMENT, user_id INTEGER, CONSTRAINT pk_characters PRIMARY KEY(id), CONSTRAINT fk_characters_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE)")
+vRP:prepare("vRP/base_tables4","CREATE TABLE IF NOT EXISTS vrp_user_data(user_id INTEGER, dkey VARCHAR(100), dvalue BLOB, CONSTRAINT pk_user_data PRIMARY KEY(user_id,dkey), CONSTRAINT fk_user_data_users FOREIGN KEY(user_id) REFERENCES vrp_users(id) ON DELETE CASCADE)")
+vRP:prepare("vRP/base_tables5","CREATE TABLE IF NOT EXISTS vrp_character_data(character_id INTEGER, dkey VARCHAR(100), dvalue BLOB, CONSTRAINT pk_character_data PRIMARY KEY(character_id,dkey), CONSTRAINT fk_character_data_characters FOREIGN KEY(character_id) REFERENCES vrp_characters(id) ON DELETE CASCADE)")
+vRP:prepare("vRP/base_tables6","CREATE TABLE IF NOT EXISTS vrp_server_data(id VARCHAR(100), dkey VARCHAR(100), dvalue BLOB, CONSTRAINT pk_server_data PRIMARY KEY(id, dkey))")
+vRP:prepare("vRP/base_tables7","CREATE TABLE IF NOT EXISTS vrp_global_data(dkey VARCHAR(100), dvalue BLOB, CONSTRAINT pk_global_data PRIMARY KEY(dkey))")
+
+vRP:prepare("vRP/create_user","INSERT INTO vrp_users(id) VALUES(DEFAULT)")
+vRP:prepare("vRP/get_last_user_id","SELECT LAST_INSERT_ID() AS id")
+
+vRP:prepare("vRP/create_character", "INSERT INTO vrp_characters(user_id) VALUES(@user_id)")
+vRP:prepare("vRP/get_last_character_id", "SELECT LAST_INSERT_ID() AS id")
vRP:prepare("vRP/delete_character", "DELETE FROM vrp_characters WHERE id = @id AND user_id = @user_id")
vRP:prepare("vRP/get_characters", "SELECT id FROM vrp_characters WHERE user_id = @user_id")
vRP:prepare("vRP/check_character", "SELECT id FROM vrp_characters WHERE id = @id AND user_id = @user_id")
vRP:prepare("vRP/add_identifier","INSERT INTO vrp_user_ids(identifier,user_id) VALUES(@identifier,@user_id)")
vRP:prepare("vRP/userid_byidentifier","SELECT user_id FROM vrp_user_ids WHERE identifier = @identifier")
+vRP:prepare("vRP/get_max_user_id","SELECT MAX(id) as max_id FROM vrp_users")
vRP:prepare("vRP/set_userdata","REPLACE INTO vrp_user_data(user_id,dkey,dvalue) VALUES(@user_id,@key,UNHEX(@value))")
vRP:prepare("vRP/get_userdata","SELECT dvalue FROM vrp_user_data WHERE user_id = @user_id AND dkey = @key")
@@ -89,7 +50,16 @@ vRP:prepare("vRP/set_globaldata","REPLACE INTO vrp_global_data(dkey,dvalue) VALU
vRP:prepare("vRP/get_globaldata","SELECT dvalue FROM vrp_global_data WHERE dkey = @key")
-- init tables
-async(function() vRP:execute("vRP/base_tables") end)
+async(function()
+ -- Create tables in dependency order
+ vRP:execute("vRP/base_tables") -- vrp_users (no dependencies)
+ vRP:execute("vRP/base_tables2") -- vrp_user_ids (depends on vrp_users)
+ vRP:execute("vRP/base_tables3") -- vrp_characters (depends on vrp_users)
+ vRP:execute("vRP/base_tables4") -- vrp_user_data (depends on vrp_users)
+ vRP:execute("vRP/base_tables5") -- vrp_character_data (depends on vrp_characters)
+ vRP:execute("vRP/base_tables6") -- vrp_server_data (no dependencies)
+ vRP:execute("vRP/base_tables7") -- vrp_global_data (no dependencies)
+end)
-- handlers
diff --git a/vrp/cfg/hud.lua b/vrp/cfg/hud.lua
new file mode 100644
index 0000000..75e22bb
--- /dev/null
+++ b/vrp/cfg/hud.lua
@@ -0,0 +1,18 @@
+-- vRP HUD configuration
+
+local cfg = {}
+
+-- HUD Module
+cfg.module_enabled = true
+
+-- Default HUD settings (these can be customized per player)
+cfg.default_settings = {
+ hud_enabled = true,
+ hud_show_health = true,
+ hud_show_armor = true,
+ hud_show_hunger = true,
+ hud_show_thirst = true,
+ hud_show_stamina = false
+}
+
+return cfg
\ No newline at end of file
diff --git a/vrp/cfg/lang/en.lua b/vrp/cfg/lang/en.lua
index 35a942a..a39cea2 100644
--- a/vrp/cfg/lang/en.lua
+++ b/vrp/cfg/lang/en.lua
@@ -776,6 +776,24 @@ local lang = {
description = "{1} [ {2} ] died",
},
},
+ hud = {
+ title = "HUD",
+ description = "Configure your HUD settings",
+ config = {
+ title = "HUD Configuration"
+ },
+ toggle = "Toggle HUD",
+ enabled = "HUD is enabled",
+ disabled = "HUD is disabled",
+ health = "Health Bar",
+ armor = "Armor Bar",
+ hunger = "Hunger Bar",
+ thirst = "Thirst Bar",
+ stamina = "Stamina Bar",
+ showing = "Showing",
+ hidden = "Hidden",
+ reset = "Reset Settings"
+ },
}
return lang
diff --git a/vrp/cfg/modules.lua b/vrp/cfg/modules.lua
index 412c8f8..1732c0f 100644
--- a/vrp/cfg/modules.lua
+++ b/vrp/cfg/modules.lua
@@ -7,6 +7,7 @@ local modules = {
admin = true,
group = true,
gui = true,
+ hud = true,
map = true,
weather = true,
misc = true,
diff --git a/vrp/client/hud.lua b/vrp/client/hud.lua
new file mode 100644
index 0000000..eba14d2
--- /dev/null
+++ b/vrp/client/hud.lua
@@ -0,0 +1,484 @@
+-- vRP HUD Client Module
+-- MIT license (see LICENSE or vrp/vRPShared.lua)
+
+if not vRP.modules.hud then return end
+
+local HUD = class("HUD", vRP.Extension)
+
+-- METHODS
+
+function HUD:__construct()
+ vRP.Extension.__construct(self)
+
+ self.hud_enabled = true
+ -- Initialize with default settings
+ self.hud_settings = {
+ hud_enabled = true,
+ hud_show_health = true,
+ hud_show_armor = true,
+ hud_show_hunger = true,
+ hud_show_thirst = true,
+ hud_show_stamina = true -- Temporarily enabled for stamina testing
+ }
+ self.player_data = {
+ health = 100,
+ armor = 0,
+ hunger = 1.0,
+ thirst = 1.0,
+ stamina = 100
+ }
+
+ -- Debug flag to help troubleshoot
+ self.debug_mode = false -- Disabled for clean operation
+
+ -- HUD update task - frequent updates
+ Citizen.CreateThread(function()
+ while true do
+ Citizen.Wait(0) -- Critical: Yield control every frame to prevent script deadlock
+
+ if self.hud_enabled and self.hud_settings.hud_enabled then
+ self:checkForUpdates()
+ else
+ Citizen.Wait(100) -- Wait when HUD is disabled
+ end
+ end
+ end)
+
+ -- Listen for player state changes
+ Citizen.CreateThread(function()
+ while true do
+ Citizen.Wait(0) -- Critical: Yield control every frame to prevent script deadlock
+
+ if self.hud_enabled and self.hud_settings.hud_enabled then
+ local update_data = {}
+
+ -- Get current health
+ if self.hud_settings.hud_show_health then
+ local current_health = GetEntityHealth(GetPlayerPed(-1))
+ if math.abs(current_health - self.player_data.health) > 1 then -- Allow small variance
+ update_data.health = current_health
+ end
+ end
+
+ -- Get current armor
+ if self.hud_settings.hud_show_armor then
+ local current_armor = GetPedArmour(GetPlayerPed(-1))
+ if math.abs(current_armor - self.player_data.armor) > 1 then -- Allow small variance
+ update_data.armor = current_armor
+ end
+ end
+
+ -- Get current stamina (real-time tracking - no threshold)
+ if self.hud_settings.hud_show_stamina then
+ local current_stamina = self:getPlayerStamina()
+ -- Always update stamina for real-time response
+ update_data.stamina = current_stamina
+ end
+
+ if next(update_data) then
+ self:updatePlayerData(update_data)
+ end
+ else
+ Citizen.Wait(100) -- Wait when HUD is disabled
+ end
+ end
+ end)
+
+ -- Dedicated stamina tracking (every frame updates)
+ Citizen.CreateThread(function()
+ while true do
+ Citizen.Wait(0) -- Critical: Yield control every frame to prevent script deadlock
+
+ if self.hud_enabled and self.hud_settings.hud_enabled and self.hud_settings.hud_show_stamina then
+ local current_stamina = self:getPlayerStamina()
+ -- Always update stamina for real-time response (no threshold)
+ self:updatePlayerData({stamina = current_stamina})
+ else
+ Citizen.Wait(100) -- Minimal wait when stamina HUD is disabled
+ end
+ end
+ end)
+
+ -- Override native stamina system (set to max every 3 seconds)
+ Citizen.CreateThread(function()
+ while true do
+ Citizen.Wait(3000) -- Update every 3 seconds
+
+ if self.hud_enabled and self.hud_settings.hud_enabled and self.hud_settings.hud_show_stamina then
+ local playerPed = GetPlayerPed(-1)
+ if DoesEntityExist(playerPed) then
+ -- Get player's maximum stamina and set current stamina to match
+ local maxStamina = GetPlayerMaxStamina(PlayerId())
+ SetPlayerStamina(PlayerId(), maxStamina)
+ end
+ end
+ end
+ end)
+
+ -- Fallback mechanism: refresh HUD after 5 seconds if no data received
+ Citizen.CreateThread(function()
+ Citizen.Wait(5000) -- Wait 5 seconds after initialization
+
+ if self.hud_enabled and self.hud_settings.hud_enabled then
+ self:refreshHUD()
+ end
+ end)
+end
+
+-- Check for updates and only update HUD when values change
+function HUD:checkForUpdates()
+ local needs_update = false
+ local update_data = {}
+
+ -- Check health
+ if self.hud_settings.hud_show_health then
+ local current_health = GetEntityHealth(GetPlayerPed(-1))
+ if math.abs(current_health - self.player_data.health) > 2 then -- Allow small variance
+ update_data.health = current_health
+ needs_update = true
+ end
+ end
+
+ -- Check armor
+ if self.hud_settings.hud_show_armor then
+ local current_armor = GetPedArmour(GetPlayerPed(-1))
+ if math.abs(current_armor - self.player_data.armor) > 2 then -- Allow small variance
+ update_data.armor = current_armor
+ needs_update = true
+ end
+ end
+
+ -- Check stamina (real detection - no threshold)
+ if self.hud_settings.hud_show_stamina then
+ local current_stamina = self:getPlayerStamina()
+ update_data.stamina = current_stamina
+ needs_update = true
+ end
+
+ if needs_update then
+ self:updatePlayerData(update_data)
+ self:updateHUD()
+ end
+end
+
+-- Update HUD display (real-time updates)
+function HUD:updateHUD()
+ -- Create a single HUD container with all elements
+ if self.hud_settings.hud_enabled then
+ self:createHUDContainer()
+ else
+ vRP.EXT.GUI:removeDiv("hud_container")
+ end
+end
+
+function HUD:createHUDContainer()
+ local elements = {}
+
+ -- Build elements in correct order based on settings
+ if self.hud_settings.hud_show_health and self.hud_settings.hud_enabled then
+ local health = tonumber(self.player_data.health) or 100
+ local health_percent = math.max(0, math.min(100, health)) / 100
+ table.insert(elements, self:createHUDElement("health", "Health: " .. math.floor(health), "rgba(255, 0, 0, 0.9)", math.floor(health_percent * 100)))
+ end
+
+ if self.hud_settings.hud_show_armor and self.hud_settings.hud_enabled then
+ local armor = tonumber(self.player_data.armor) or 0
+ local armor_percent = math.max(0, math.min(100, armor)) / 100
+ table.insert(elements, self:createHUDElement("armor", "Armor: " .. math.floor(armor), "rgba(0, 0, 255, 0.9)", math.floor(armor_percent * 100)))
+ end
+
+ if self.hud_settings.hud_show_hunger and self.hud_settings.hud_enabled then
+ local hunger = tonumber(self.player_data.hunger) or 1.0
+ local hunger_percent = math.max(0, math.min(1, hunger))
+ table.insert(elements, self:createHUDElement("hunger", "Hunger: " .. math.floor(hunger_percent * 100) .. "%", "rgba(255, 165, 0, 0.9)", math.floor(hunger_percent * 100)))
+ end
+
+ if self.hud_settings.hud_show_thirst and self.hud_settings.hud_enabled then
+ local thirst = tonumber(self.player_data.thirst) or 1.0
+ local thirst_percent = math.max(0, math.min(1, thirst))
+ table.insert(elements, self:createHUDElement("thirst", "Thirst: " .. math.floor(thirst_percent * 100) .. "%", "rgba(0, 191, 255, 0.9)", math.floor(thirst_percent * 100)))
+ end
+
+ if self.hud_settings.hud_show_stamina and self.hud_settings.hud_enabled then
+ local stamina = tonumber(self.player_data.stamina) or 100
+ local stamina_percent = math.max(0, math.min(100, stamina)) / 100
+ table.insert(elements, self:createHUDElement("stamina", "Stamina: " .. math.floor(stamina) .. "%", "rgba(0, 255, 0, 0.9)", math.floor(stamina_percent * 100)))
+ end
+
+ -- Create container HTML
+ local hud_html = string.format([[
+
+ %s
+
+ ]], table.concat(elements, ""))
+
+ vRP.EXT.GUI:setDiv("hud_container", "", hud_html)
+end
+
+function HUD:createHUDElement(element, text, bar_color, bar_width)
+ return string.format([[
+
+ ]], element, bar_color, bar_color, bar_width, text)
+end
+
+-- Update player data
+function HUD:updatePlayerData(data)
+ for k, v in pairs(data) do
+ self.player_data[k] = tonumber(v) or v
+ end
+end
+
+-- Set HUD enabled/disabled
+function HUD:setHUDEnabled(enabled)
+ self.hud_enabled = enabled
+
+ if not enabled then
+ -- Remove HUD container
+ vRP.EXT.GUI:removeDiv("hud_container")
+ else
+ -- Re-enable HUD - force immediate update
+ self:updateHUD()
+ end
+end
+
+-- Update HUD settings
+function HUD:updateHUDSettings(settings)
+ -- Store old enabled state
+ local was_enabled = self.hud_settings.hud_enabled
+
+ self.hud_settings = settings
+
+ -- Update individual settings with defaults if not provided
+ if settings.hud_show_health == nil then settings.hud_show_health = true end
+ if settings.hud_show_armor == nil then settings.hud_show_armor = true end
+ if settings.hud_show_hunger == nil then settings.hud_show_hunger = true end
+ if settings.hud_show_thirst == nil then settings.hud_show_thirst = true end
+ if settings.hud_show_stamina == nil then settings.hud_show_stamina = false end
+
+ -- Handle HUD enabled/disabled state changes
+ if not settings.hud_enabled then
+ self:setHUDEnabled(false)
+ elseif not was_enabled and settings.hud_enabled then
+ self:setHUDEnabled(true)
+ elseif settings.hud_enabled then
+ self:updateHUD()
+ end
+end
+
+-- Update individual HUD element value
+function HUD:updateHUDElement(element, value, percent, text)
+ if not self.hud_enabled or not self.hud_settings.hud_enabled then return end
+
+ local div_name = "hud_" .. element
+
+ -- Create complete HTML structure with CSS and JS
+ local html_content = string.format([[
+
+ ]],
+ element,
+ self:getElementPosition(element),
+ element,
+ self:getElementColor(element),
+ math.floor(percent * 100),
+ text
+ )
+
+ vRP.EXT.GUI:setDiv(div_name, "", html_content)
+end
+
+function HUD:getElementPosition(element)
+ local positions = {
+ health = "130px",
+ armor = "105px",
+ hunger = "80px",
+ thirst = "55px",
+ stamina = "30px"
+ }
+ return positions[element] or "80px"
+end
+
+function HUD:getElementColor(element)
+ local colors = {
+ health = "rgba(255, 0, 0, 0.9)",
+ armor = "rgba(0, 0, 255, 0.9)",
+ hunger = "rgba(255, 165, 0, 0.9)",
+ thirst = "rgba(0, 191, 255, 0.9)",
+ stamina = "rgba(0, 255, 0, 0.9)"
+ }
+ return colors[element] or "rgba(255, 255, 255, 0.9)"
+end
+
+-- Get player's current stamina level (0-100)
+function HUD:getPlayerStamina()
+ local playerPed = GetPlayerPed(-1)
+
+ -- Check if player ped exists
+ if not DoesEntityExist(playerPed) then
+ return 100
+ end
+
+ -- Independent stamina tracking system (ignores game's stamina)
+ local currentTime = GetGameTimer()
+
+ -- Initialize stamina tracking if not exists
+ if not self.staminaData then
+ self.staminaData = {
+ currentStamina = 100,
+ lastUpdate = currentTime
+ }
+ end
+
+ -- Calculate time elapsed since last update
+ local deltaTime = currentTime - self.staminaData.lastUpdate
+ local deltaSeconds = deltaTime / 1000.0
+
+ -- Check if player is trying to sprint (independent of game's stamina)
+ local isTryingToSprint = IsControlPressed(0, 21) -- Left Shift key
+
+ -- Update stamina based on sprint attempts
+ if isTryingToSprint and self.staminaData.currentStamina > 0 then
+ -- Random stamina drain between 2.5% to 5% per second when sprinting
+ local randomDrain = math.random(25, 50) / 10.0 -- Random between 2.5-5.0
+ self.staminaData.currentStamina = self.staminaData.currentStamina - (randomDrain * deltaSeconds)
+
+ -- If stamina hits zero, force player out of sprint
+ if self.staminaData.currentStamina <= 0 then
+ self.staminaData.currentStamina = 0
+ SetPedMaxMoveBlendRatio(playerPed, 1.0) -- Force walk speed
+ end
+
+ if self.debug_mode then
+ print(string.format("[HUD DEBUG] Sprinting - Drain: %.2f%%/sec, Stamina: %.1f%%",
+ randomDrain, self.staminaData.currentStamina))
+ end
+ else
+ -- Regenerate stamina while not sprinting (1.5% per second)
+ self.staminaData.currentStamina = self.staminaData.currentStamina + (1.5 * deltaSeconds)
+
+ -- Allow normal sprint speed when stamina is above 10%
+ if self.staminaData.currentStamina > 10 then
+ SetPedMaxMoveBlendRatio(playerPed, 2.6) -- Allow sprint speed
+ end
+ end
+
+ -- Clamp stamina between 0 and 100
+ self.staminaData.currentStamina = math.max(0, math.min(100, self.staminaData.currentStamina))
+
+ -- Update last update time
+ self.staminaData.lastUpdate = currentTime
+
+ -- Debug output disabled to prevent console spam
+ -- if self.debug_mode then
+ -- print(string.format("[HUD DEBUG] Trying to Sprint: %s, Stamina: %.1f%%",
+ -- tostring(isTryingToSprint), self.staminaData.currentStamina))
+ -- end
+
+ return self.staminaData.currentStamina
+end
+
+-- Force refresh HUD display (fallback function)
+function HUD:refreshHUD()
+ -- Ensure we have valid data
+ if not self.player_data.health then self.player_data.health = 100 end
+ if not self.player_data.armor then self.player_data.armor = 0 end
+ if not self.player_data.hunger then self.player_data.hunger = 1.0 end
+ if not self.player_data.thirst then self.player_data.thirst = 1.0 end
+ if not self.player_data.stamina then self.player_data.stamina = 100 end
+
+ self:updateHUD()
+end
+
+-- EVENT
+
+HUD.event = {}
+
+function HUD.event:NUIReady()
+ if self.hud_settings.hud_enabled then
+ self:updateHUD()
+ end
+end
+
+-- TUNNEL
+
+HUD.tunnel = {}
+
+HUD.tunnel.updatePlayerData = HUD.updatePlayerData
+HUD.tunnel.setHUDEnabled = HUD.setHUDEnabled
+HUD.tunnel.updateHUDSettings = HUD.updateHUDSettings
+HUD.tunnel.refreshHUD = HUD.refreshHUD
+
+vRP:registerExtension(HUD)
\ No newline at end of file
diff --git a/vrp/client/player_state.lua b/vrp/client/player_state.lua
index 0d5a99b..75c423c 100644
--- a/vrp/client/player_state.lua
+++ b/vrp/client/player_state.lua
@@ -41,6 +41,7 @@ function PlayerState:setHealth(amount)
SetEntityHealth(GetPlayerPed(-1), math.floor(amount))
end
+
function PlayerState:getHealth()
return GetEntityHealth(GetPlayerPed(-1))
end
diff --git a/vrp/fxmanifest.lua b/vrp/fxmanifest.lua
index 23ccbdb..b16d8d9 100644
--- a/vrp/fxmanifest.lua
+++ b/vrp/fxmanifest.lua
@@ -16,6 +16,8 @@ server_script {
"modules/gui.lua",
"modules/admin.lua",
"modules/group.lua",
+ "modules/money.lua",
+ "modules/hud.lua",
"modules/identity.lua",
"modules/weather.lua",
"modules/commands.lua",
@@ -29,6 +31,7 @@ client_scripts {
"client/base.lua",
"client/map.lua",
"client/gui.lua",
+ "client/hud.lua",
"client/identity.lua",
"client/admin.lua",
"client/weather.lua",
diff --git a/vrp/modules/hud.lua b/vrp/modules/hud.lua
new file mode 100644
index 0000000..5bbaba6
--- /dev/null
+++ b/vrp/modules/hud.lua
@@ -0,0 +1,281 @@
+-- vRP HUD Module
+-- MIT license (see LICENSE or vrp/vRPShared.lua)
+
+if not vRP.modules.hud then return end
+
+local lang = vRP.lang
+
+local HUD = class("HUD", vRP.Extension)
+
+-- METHODS
+
+function HUD:__construct()
+ vRP.Extension.__construct(self)
+
+ self.cfg = module("vrp", "cfg/hud")
+
+ -- Register HUD configuration menu
+ vRP.EXT.GUI:registerMenuBuilder("main", function(menu)
+ local user = menu.user
+ menu:addOption(lang.hud.title(), function(menu)
+ user:openMenu("hud_config")
+ end, lang.hud.description())
+ end)
+
+ -- Register HUD config menu
+ vRP.EXT.GUI:registerMenuBuilder("hud_config", function(menu)
+ local user = menu.user
+ menu.title = lang.hud.config.title()
+ menu.css.header_color = "rgba(0,125,255,0.75)"
+
+ -- Toggle HUD
+ menu:addOption(lang.hud.toggle(), function(menu)
+ local hud_enabled = user.cdata.hud_enabled
+ if hud_enabled == nil then hud_enabled = true end
+ user.cdata.hud_enabled = not hud_enabled
+
+ -- Send both the enabled state and the settings to ensure proper re-enabling
+ self.remote._setHUDEnabled(user.source, not hud_enabled)
+ if user.cdata.hud_enabled then
+ -- If re-enabling, send current settings to client
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ end
+
+ user:actualizeMenu()
+ end, function(menu)
+ local hud_enabled = user.cdata.hud_enabled
+ if hud_enabled == nil then hud_enabled = true end
+ return hud_enabled and lang.hud.enabled() or lang.hud.disabled()
+ end)
+
+ -- Health bar toggle
+ menu:addOption(lang.hud.health(), function(menu)
+ local show_health = user.cdata.hud_show_health
+ if show_health == nil then show_health = true end
+ user.cdata.hud_show_health = not show_health
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end, function(menu)
+ local show_health = user.cdata.hud_show_health
+ if show_health == nil then show_health = true end
+ return (show_health and lang.hud.showing() or lang.hud.hidden()) .. " " .. lang.hud.health()
+ end)
+
+ -- Armor bar toggle
+ menu:addOption(lang.hud.armor(), function(menu)
+ local show_armor = user.cdata.hud_show_armor
+ if show_armor == nil then show_armor = true end
+ user.cdata.hud_show_armor = not show_armor
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end, function(menu)
+ local show_armor = user.cdata.hud_show_armor
+ if show_armor == nil then show_armor = true end
+ return (show_armor and lang.hud.showing() or lang.hud.hidden()) .. " " .. lang.hud.armor()
+ end)
+
+ -- Hunger bar toggle
+ menu:addOption(lang.hud.hunger(), function(menu)
+ local show_hunger = user.cdata.hud_show_hunger
+ if show_hunger == nil then show_hunger = true end
+ user.cdata.hud_show_hunger = not show_hunger
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end, function(menu)
+ local show_hunger = user.cdata.hud_show_hunger
+ if show_hunger == nil then show_hunger = true end
+ return (show_hunger and lang.hud.showing() or lang.hud.hidden()) .. " " .. lang.hud.hunger()
+ end)
+
+ -- Thirst bar toggle
+ menu:addOption(lang.hud.thirst(), function(menu)
+ local show_thirst = user.cdata.hud_show_thirst
+ if show_thirst == nil then show_thirst = true end
+ user.cdata.hud_show_thirst = not show_thirst
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end, function(menu)
+ local show_thirst = user.cdata.hud_show_thirst
+ if show_thirst == nil then show_thirst = true end
+ return (show_thirst and lang.hud.showing() or lang.hud.hidden()) .. " " .. lang.hud.thirst()
+ end)
+
+ -- Stamina bar toggle
+ menu:addOption(lang.hud.stamina(), function(menu)
+ local show_stamina = user.cdata.hud_show_stamina
+ if show_stamina == nil then show_stamina = false end
+ user.cdata.hud_show_stamina = not show_stamina
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end, function(menu)
+ local show_stamina = user.cdata.hud_show_stamina
+ if show_stamina == nil then show_stamina = false end
+ return (show_stamina and lang.hud.showing() or lang.hud.hidden()) .. " " .. lang.hud.stamina()
+ end)
+
+ -- Reset HUD settings
+ menu:addOption(lang.hud.reset(), function(menu)
+ user.cdata.hud_enabled = nil
+ user.cdata.hud_show_health = nil
+ user.cdata.hud_show_armor = nil
+ user.cdata.hud_show_hunger = nil
+ user.cdata.hud_show_thirst = nil
+ user.cdata.hud_show_stamina = nil
+ self.remote._updateHUDSettings(user.source, user.cdata)
+ user:actualizeMenu()
+ end)
+ end)
+end
+
+-- EVENT
+
+HUD.event = {}
+
+function HUD.event:playerSpawn(user, first_spawn)
+ if first_spawn then
+ -- Initialize HUD data if not exists
+ if not user.cdata.hud_settings then
+ user.cdata.hud_settings = {}
+ end
+
+ -- Set default HUD settings
+ if user.cdata.hud_enabled == nil then
+ user.cdata.hud_enabled = true
+ end
+ if user.cdata.hud_show_health == nil then
+ user.cdata.hud_show_health = true
+ end
+ if user.cdata.hud_show_armor == nil then
+ user.cdata.hud_show_armor = true
+ end
+ if user.cdata.hud_show_hunger == nil then
+ user.cdata.hud_show_hunger = true
+ end
+ if user.cdata.hud_show_thirst == nil then
+ user.cdata.hud_show_thirst = true
+ end
+ if user.cdata.hud_show_stamina == nil then
+ user.cdata.hud_show_stamina = false
+ end
+
+ -- Send HUD settings to client immediately
+ self.remote._updateHUDSettings(user.source, user.cdata)
+
+ -- Send initial player data after a short delay to ensure client is ready
+ Citizen.Wait(1500) -- Increased delay to ensure client is fully ready
+
+ local initial_data = {
+ health = 100,
+ armor = 0,
+ hunger = 1.0,
+ thirst = 1.0,
+ stamina = 100
+ }
+
+ -- Get actual player data if modules exist
+ if vRP.EXT.PlayerState then
+ initial_data.health = vRP.EXT.PlayerState.remote.getHealth(user.source) or 100
+ initial_data.armor = vRP.EXT.PlayerState.remote.getArmour(user.source) or 0
+ end
+
+ -- Ensure all values are properly typed
+ initial_data.health = tonumber(initial_data.health) or 100
+ initial_data.armor = tonumber(initial_data.armor) or 0
+ initial_data.hunger = tonumber(initial_data.hunger) or 1.0
+ initial_data.thirst = tonumber(initial_data.thirst) or 1.0
+ initial_data.stamina = tonumber(initial_data.stamina) or 100
+
+ -- Send to client
+ self.remote._updatePlayerData(user.source, initial_data)
+
+ -- Fallback: refresh HUD after additional delay if needed
+ Citizen.CreateThread(function()
+ Citizen.Wait(3000) -- Wait 3 more seconds
+ if user and user:isReady() then
+ self.remote._refreshHUD(user.source)
+ end
+ end)
+ end
+end
+
+function HUD.event:playerDeath(user)
+ -- Update HUD when player dies
+ if vRP.EXT.PlayerState then
+ self.remote._updatePlayerData(user.source, {
+ health = vRP.EXT.PlayerState.remote.getHealth(user.source) or 0,
+ armor = vRP.EXT.PlayerState.remote.getArmour(user.source) or 0
+ })
+ end
+end
+
+-- Listen for player state updates
+function HUD.event:playerStateUpdate(user, state)
+ if user.cdata.hud_enabled then
+ local update_data = {}
+
+ if state.health then
+ update_data.health = state.health
+ end
+
+ if update_data.health or state.armor then
+ update_data.armor = state.armor or (vRP.EXT.PlayerState and vRP.EXT.PlayerState.remote.getArmour(user.source) or 0)
+ self.remote._updatePlayerData(user.source, update_data)
+ end
+ end
+end
+
+-- TUNNEL
+
+HUD.tunnel = {}
+
+function HUD.tunnel:updateHUDSettings(settings)
+ local user = vRP.users_by_source[source]
+ if user and user:isReady() then
+ for k, v in pairs(settings) do
+ user.cdata[k] = v
+ end
+ end
+end
+
+-- REMOTE
+
+HUD.remote = {}
+
+function HUD.remote:_updateHUDSettings(source, settings)
+ local user = vRP.users_by_source[source]
+ if user and user:isReady() then
+ for k, v in pairs(settings) do
+ user.cdata[k] = v
+ end
+ end
+end
+
+function HUD.remote:_setHUDEnabled(source, enabled)
+ vRP.EXT.GUI.remote.setVisible(source, enabled)
+
+ -- If re-enabling HUD, also send current settings to ensure proper display
+ if enabled then
+ local user = vRP.users_by_source[source]
+ if user and user.cdata then
+ self.remote._updateHUDSettings(source, user.cdata)
+ end
+ end
+end
+
+function HUD.remote:_updatePlayerData(source, data)
+ -- Send updated player data to client
+ local user = vRP.users_by_source[source]
+ if user and user:isReady() then
+ self.remote.updatePlayerData(source, data)
+ end
+end
+
+function HUD.remote:_refreshHUD(source)
+ -- Force client to refresh HUD display
+ local user = vRP.users_by_source[source]
+ if user and user:isReady() then
+ self.remote.refreshHUD(source)
+ end
+end
+
+vRP:registerExtension(HUD)
\ No newline at end of file
diff --git a/vrp/modules/identity.lua b/vrp/modules/identity.lua
index 9848fb6..82ac6e0 100644
--- a/vrp/modules/identity.lua
+++ b/vrp/modules/identity.lua
@@ -264,8 +264,10 @@ end
function Identity.event:playerSpawn(user, first_spawn)
if first_spawn then
- -- send registration number to client at spawn
- self.remote._setRegistrationNumber(user.source, user.identity.registration)
+ -- send registration number to client at spawn (check if identity exists)
+ if user.identity and user.identity.registration then
+ self.remote._setRegistrationNumber(user.source, user.identity.registration)
+ end
-- build city hall
local menu
@@ -288,7 +290,9 @@ function Identity.event:playerSpawn(user, first_spawn)
end
function Identity.event:characterIdentityUpdate(user)
- self.remote._setRegistrationNumber(user.source, user.identity.registration)
+ if user.identity and user.identity.registration then
+ self.remote._setRegistrationNumber(user.source, user.identity.registration)
+ end
end
vRP:registerExtension(Identity)
\ No newline at end of file
diff --git a/vrp/modules/money.lua b/vrp/modules/money.lua
index eb6974d..7685b07 100644
--- a/vrp/modules/money.lua
+++ b/vrp/modules/money.lua
@@ -57,14 +57,20 @@ end
-- Deduct from wallet if enough funds exist.
-- 'dry' mode allows checking without modifying data.
function Money.User:tryPayment(amount, dry)
- if type(amount) ~= "number" or amount <= 0 then return false end
- if self:getWallet() >= amount then
- if not dry then
- self:setWallet(self:getWallet() - amount)
- end
- return true
- end
- return false
+ if type(amount) ~= "number" then return false end
+
+ -- Allow zero-cost payments (free items/services)
+ if amount <= 0 then
+ return true -- Always succeed for free items
+ end
+
+ if self:getWallet() >= amount then
+ if not dry then
+ self:setWallet(self:getWallet() - amount)
+ end
+ return true
+ end
+ return false
end
-- Withdraw from bank into wallet.
diff --git a/vrp/vRP.lua b/vrp/vRP.lua
index c9b65e6..402ada1 100644
--- a/vrp/vRP.lua
+++ b/vrp/vRP.lua
@@ -215,14 +215,39 @@ function vRP:authUser(source)
if #rows > 0 then return rows[1].user_id end
end
-- no ids found, create user
- local rows, affected = self:query("vRP/create_user", {})
- if #rows > 0 then
- local user_id = rows[1].id
- -- add identifiers
- for _, id in pairs(ids) do
- self:execute("vRP/add_identifier", {user_id = user_id, identifier = id})
+ local ok = self:execute("vRP/create_user", {})
+ if ok then
+ Citizen.Wait(100) -- Small delay to ensure the insert is processed
+ local rows = self:query("vRP/get_last_user_id", {})
+ if #rows > 0 then
+ local user_id = rows[1].id
+ if user_id and user_id > 0 then
+ -- add identifiers
+ for _, id in pairs(ids) do
+ local add_ok = self:execute("vRP/add_identifier", {user_id = user_id, identifier = id})
+ if not add_ok then
+ self:error("Failed to add identifier: "..id.." for user_id: "..user_id)
+ end
+ end
+ return user_id
+ else
+ self:error("Invalid user_id returned from LAST_INSERT_ID: "..(user_id or "nil")..", rows: "..json.encode(rows))
+ -- Fallback: try to get the maximum user_id + 1
+ local max_rows = self:query("vRP/get_max_user_id", {})
+ if #max_rows > 0 then
+ local fallback_id = (max_rows[1].max_id or 0) + 1
+ self:error("Using fallback user_id: "..fallback_id)
+ for _, id in pairs(ids) do
+ self:execute("vRP/add_identifier", {user_id = fallback_id, identifier = id})
+ end
+ return fallback_id
+ end
+ end
+ else
+ self:error("No rows returned from get_last_user_id query")
end
- return user_id
+ else
+ self:error("Failed to execute create_user query, result: "..ok)
end
end
@@ -254,11 +279,29 @@ function vRP:connectUser(source)
if type(data) == "table" then user.data = data end
end
--- character
- if not user:useCharacter(user.data.current_character or 0) then -- use last used character
+ local current_char = user.data.current_character
+ if current_char and current_char > 0 then
+ -- Try to use the last used character
+ if not user:useCharacter(current_char) then
+ -- Fallback to first available character or create new one
+ local characters = user:getCharacters()
+ if #characters > 0 then
+ user:useCharacter(characters[1])
+ else
+ local cid = user:createCharacter()
+ if cid then
+ user:useCharacter(cid)
+ else
+ self:error("couldn't create character (user_id = "..user_id..")")
+ end
+ end
+ end
+ else
+ -- No previous character, get first available or create new one
local characters = user:getCharacters()
- if #characters > 0 then -- use existing character
+ if #characters > 0 then
user:useCharacter(characters[1])
- else -- use new character
+ else
local cid = user:createCharacter()
if cid then
user:useCharacter(cid)