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([[ +
+
+
%s
+
+ ]], 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([[ +
+
+
%s
+
+ ]], + 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)