-- client/appearance.lua 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") end local function ForcePlayerModel(modelName) local model = GetHashKey(modelName) if not IsModelInCdimage(model) or not IsModelValid(model) then print(("^1[turfwar]^7 INVALID model: %s"):format(modelName)) return false end RequestModel(model) local timeout = GetGameTimer() + 8000 while not HasModelLoaded(model) do Wait(0) if GetGameTimer() > timeout then print(("^1[turfwar]^7 TIMEOUT loading model: %s"):format(modelName)) return false end end SetPlayerModel(PlayerId(), model) SetModelAsNoLongerNeeded(model) Wait(200) 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 local ped = PlayerPedId() if not ped or ped == 0 then return end -- 1) Force model local desired = (appearance.gender == "f") and "mp_f_freemode_01" or "mp_m_freemode_01" if GetEntityModel(ped) ~= GetHashKey(desired) then ForcePlayerModel(desired) ped = PlayerPedId() if not ped or ped == 0 then return 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).") return end -- 3) Hair style validation (component 2) local hairDrawable = tonumber(appearance.hair_drawable) or 0 local hairTexture = tonumber(appearance.hair_texture) or 0 local maxHair = GetNumberOfPedDrawableVariations(ped, 2) if maxHair > 0 then hairDrawable = clamp(hairDrawable, 0, maxHair - 1) local maxTex = GetNumberOfPedTextureVariations(ped, 2, hairDrawable) if maxTex <= 0 then maxTex = 1 end hairTexture = clamp(hairTexture, 0, maxTex - 1) SetPedComponentVariation(ped, 2, hairDrawable, hairTexture, 0) end -- 4) Hair color local c = tonumber(appearance.hair_color) or 0 local h = tonumber(appearance.hair_highlight) if h == nil then h = c end local numColors = GetNumHairColors() if numColors and numColors > 0 then c = clamp(c, 0, numColors - 1) h = clamp(h, 0, numColors - 1) else c = clamp(c, 0, 63) h = clamp(h, 0, 63) end SetPedHairColor(ped, c, h) print(("[turfwar] appearance applied: gender=%s hair=%d/%d color=%d/%d") :format(tostring(appearance.gender), hairDrawable, hairTexture, c, h)) 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 AddEventHandler("onClientResourceStart", function(res) if res ~= GetCurrentResourceName() then return end CreateThread(function() Wait(500) TriggerServerEvent("turfwar:appearance:request") end) end) AddEventHandler("playerSpawned", function() CreateThread(function() Wait(700) TriggerServerEvent("turfwar:appearance:request") end) 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) cb({ ok = true }) end) RegisterNUICallback("appearance:hairNext", function(_, cb) if not appearance then cb({ ok=false }) return end local ped = PlayerPedId() if ped == 0 then cb({ ok=false }) return end local maxHair = GetNumberOfPedDrawableVariations(ped, 2) if maxHair <= 0 then cb({ ok=false }) return end local cur = GetPedDrawableVariation(ped, 2) local nxt = (cur + 1) % maxHair TriggerServerEvent("turfwar:appearance:setHair", nxt, 0, appearance.hair_color, appearance.hair_highlight) cb({ ok = true }) end) RegisterNUICallback("appearance:hairPrev", function(_, cb) if not appearance then cb({ ok=false }) return end local ped = PlayerPedId() if ped == 0 then cb({ ok=false }) return end local maxHair = GetNumberOfPedDrawableVariations(ped, 2) if maxHair <= 0 then cb({ ok=false }) return end local cur = GetPedDrawableVariation(ped, 2) local prv = cur - 1 if prv < 0 then prv = maxHair - 1 end TriggerServerEvent("turfwar:appearance:setHair", prv, 0, appearance.hair_color, appearance.hair_highlight) cb({ ok = true }) end) RegisterNUICallback("appearance:colorNext", 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) % 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)