diff --git a/client/appearance.lua b/client/appearance.lua index 6f671fa..34f80f6 100644 --- a/client/appearance.lua +++ b/client/appearance.lua @@ -2,6 +2,7 @@ print("^2[turfwar]^7 appearance.lua loaded (client)") local appearance = nil +local choosing = false local function IsFreemodeModel(hash) return hash == GetHashKey("mp_m_freemode_01") or hash == GetHashKey("mp_f_freemode_01") @@ -30,6 +31,74 @@ local function ForcePlayerModel(modelName) return (GetEntityModel(PlayerPedId()) == model) end +local function ApplyDefaultHeadBlend(ped, gender) + if gender == "f" then + SetPedHeadBlendData(ped, + 21, 0, 0, + 21, 0, 0, + 0.55, 0.0, 0.0, + false + ) + else + SetPedHeadBlendData(ped, + 0, 0, 0, + 0, 0, 0, + 0.5, 0.5, 0.0, + false + ) + end + + for i = 0, 19 do + SetPedFaceFeature(ped, i, 0.0) + end +end + +local function clamp(n, a, b) + if n < a then return a end + if n > b then return b end + return n +end + +local function SendStateToUI() + if not appearance then return end + SendNUIMessage({ + type = "appearance:state", + gender = appearance.gender or "m", + hair_drawable = tonumber(appearance.hair_drawable) or 0, + hair_color = tonumber(appearance.hair_color) or 0 + }) +end + +local function OpenChooser() + if choosing then return end + choosing = true + SetNuiFocus(true, true) + SetNuiFocusKeepInput(true) + SendNUIMessage({ type = "appearance:show" }) + SendStateToUI() + + -- light input lock so they don't move/shoot while choosing + CreateThread(function() + while choosing do + DisableAllControlActions(0) + -- allow looking around / mouse + EnableControlAction(0, 1, true) -- LookLeftRight + EnableControlAction(0, 2, true) -- LookUpDown + EnableControlAction(0, 239, true) -- Cursor X + EnableControlAction(0, 240, true) -- Cursor Y + Wait(0) + end + end) +end + +local function CloseChooser() + if not choosing then return end + choosing = false + SendNUIMessage({ type = "appearance:hide" }) + SetNuiFocus(false, false) + SetNuiFocusKeepInput(false) +end + local function ApplyAppearanceNow() if not appearance then return end @@ -43,30 +112,9 @@ local function ApplyAppearanceNow() ped = PlayerPedId() if not ped or ped == 0 then return end end - local function ApplyDefaultHeadBlend(ped, gender) - if gender == "f" then - -- softer defaults for female - SetPedHeadBlendData(ped, - 21, 0, 0, -- shape (mother-ish) - 21, 0, 0, -- skin - 0.55, 0.0, 0.0, - false - ) - else - SetPedHeadBlendData(ped, - 0, 0, 0, - 0, 0, 0, - 0.5, 0.5, 0.0, - false - ) - end - -- neutralize extreme face features (optional) - for i = 0, 19 do - SetPedFaceFeature(ped, i, 0.0) - end -end ApplyDefaultHeadBlend(ped, appearance.gender) + -- 2) Ensure freemode if not IsFreemodeModel(GetEntityModel(ped)) then print("^1[turfwar]^7 ApplyAppearanceNow: not freemode even after force (something else is overriding model).") @@ -79,13 +127,11 @@ end local maxHair = GetNumberOfPedDrawableVariations(ped, 2) if maxHair > 0 then - if hairDrawable < 0 then hairDrawable = 0 end - if hairDrawable >= maxHair then hairDrawable = maxHair - 1 end + hairDrawable = clamp(hairDrawable, 0, maxHair - 1) local maxTex = GetNumberOfPedTextureVariations(ped, 2, hairDrawable) if maxTex <= 0 then maxTex = 1 end - if hairTexture < 0 then hairTexture = 0 end - if hairTexture >= maxTex then hairTexture = maxTex - 1 end + hairTexture = clamp(hairTexture, 0, maxTex - 1) SetPedComponentVariation(ped, 2, hairDrawable, hairTexture, 0) end @@ -97,12 +143,11 @@ end local numColors = GetNumHairColors() if numColors and numColors > 0 then - c = math.max(0, math.min(c, numColors - 1)) - h = math.max(0, math.min(h, numColors - 1)) + c = clamp(c, 0, numColors - 1) + h = clamp(h, 0, numColors - 1) else - -- fallback clamp - c = math.max(0, math.min(c, 63)) - h = math.max(0, math.min(h, 63)) + c = clamp(c, 0, 63) + h = clamp(h, 0, 63) end SetPedHairColor(ped, c, h) @@ -110,13 +155,22 @@ end print(("[turfwar] appearance applied: gender=%s hair=%d/%d color=%d/%d") :format(tostring(appearance.gender), hairDrawable, hairTexture, c, h)) - -- Tell main.lua (optional) that appearance has been applied TriggerEvent("turfwar:appearance:applied") end RegisterNetEvent("turfwar:appearance:update", function(row) appearance = row ApplyAppearanceNow() + + -- If server says this was a fresh insert, open chooser + if row and row.isNew then + OpenChooser() + end + + -- Keep UI numbers in sync while choosing + if choosing then + SendStateToUI() + end end) -- Request on resource start & spawn @@ -135,67 +189,83 @@ AddEventHandler("playerSpawned", function() end) end) --- Commands -RegisterCommand("tw_gender", function(_, args) - local g = args[1] and args[1]:lower() or "" - if g ~= "m" and g ~= "f" then - print("^3[turfwar]^7 Usage: /tw_gender m OR /tw_gender f") - return - end +-- ========================= +-- NUI callbacks +-- ========================= +RegisterNUICallback("appearance:setGender", function(data, cb) + local g = (data and data.gender) or "m" + if g ~= "m" and g ~= "f" then g = "m" end TriggerServerEvent("turfwar:appearance:setGender", g) -end, false) + cb({ ok = true }) +end) -RegisterCommand("tw_hair_next", function() - if not appearance then return end +RegisterNUICallback("appearance:hairNext", function(_, cb) + if not appearance then cb({ ok=false }) return end local ped = PlayerPedId() - if ped == 0 then return end + if ped == 0 then cb({ ok=false }) return end local maxHair = GetNumberOfPedDrawableVariations(ped, 2) - if maxHair <= 0 then - print("^1[turfwar]^7 No hair variations on this model.") - return - end + if maxHair <= 0 then cb({ ok=false }) return end local cur = GetPedDrawableVariation(ped, 2) local nxt = (cur + 1) % maxHair - local maxTex = GetNumberOfPedTextureVariations(ped, 2, nxt) - if maxTex <= 0 then maxTex = 1 end - local tex = 0 - TriggerServerEvent("turfwar:appearance:setHair", nxt, tex, appearance.hair_color, appearance.hair_highlight) -end, false) + TriggerServerEvent("turfwar:appearance:setHair", nxt, 0, appearance.hair_color, appearance.hair_highlight) + cb({ ok = true }) +end) -RegisterCommand("tw_hair_prev", function() - if not appearance then return end +RegisterNUICallback("appearance:hairPrev", function(_, cb) + if not appearance then cb({ ok=false }) return end local ped = PlayerPedId() - if ped == 0 then return end + if ped == 0 then cb({ ok=false }) return end local maxHair = GetNumberOfPedDrawableVariations(ped, 2) - if maxHair <= 0 then return end + if maxHair <= 0 then cb({ ok=false }) return end local cur = GetPedDrawableVariation(ped, 2) - local prv = (cur - 1) + local prv = cur - 1 if prv < 0 then prv = maxHair - 1 end TriggerServerEvent("turfwar:appearance:setHair", prv, 0, appearance.hair_color, appearance.hair_highlight) -end, false) + cb({ ok = true }) +end) -RegisterCommand("tw_haircolor", function(_, args) - if not appearance then return end - local c = tonumber(args[1]) - local h = tonumber(args[2]) - if c == nil then - print("^3[turfwar]^7 Usage: /tw_haircolor [highlight]") - return - end - if h == nil then h = c end - TriggerServerEvent("turfwar:appearance:setHair", appearance.hair_drawable, appearance.hair_texture, c, h) -end, false) +RegisterNUICallback("appearance:colorNext", function(_, cb) + if not appearance then cb({ ok=false }) return end -RegisterCommand("tw_appearance", function() - print("^3[turfwar]^7 Commands:") - print("^3[turfwar]^7 /tw_gender m|f") - print("^3[turfwar]^7 /tw_hair_next (cycle)") - print("^3[turfwar]^7 /tw_hair_prev (cycle)") - print("^3[turfwar]^7 /tw_haircolor [highlight]") + local num = GetNumHairColors() + if not num or num <= 0 then num = 64 end + + local c = tonumber(appearance.hair_color) or 0 + c = (c + 1) % num + + TriggerServerEvent("turfwar:appearance:setHair", appearance.hair_drawable, appearance.hair_texture, c, c) + cb({ ok = true }) +end) + +RegisterNUICallback("appearance:colorPrev", function(_, cb) + if not appearance then cb({ ok=false }) return end + + local num = GetNumHairColors() + if not num or num <= 0 then num = 64 end + + local c = tonumber(appearance.hair_color) or 0 + c = c - 1 + if c < 0 then c = num - 1 end + + TriggerServerEvent("turfwar:appearance:setHair", appearance.hair_drawable, appearance.hair_texture, c, c) + cb({ ok = true }) +end) + +RegisterNUICallback("appearance:save", function(_, cb) + -- Nothing special needed: we already write to DB on every click + CloseChooser() + cb({ ok = true }) +end) + +-- ========================= +-- Keep your commands too (optional) +-- ========================= +RegisterCommand("tw_appearance_ui", function() + OpenChooser() end, false)