Hello all,
First of all, sorry if it’s the wrong place to ask my question, but I didn’t know where to ask somewhere else.
I began scripting in Lua a few days before. I have a lot of experience with other languages (Java, Javascript, Angular, Ruby, Python and others) so developping in Lua isn’t quite a problem for me.
Some friends wanted helps on some of their scripts, so I took the challenge.
I began with esx_tatoos because this plug-in doesn’t have all DLC and they wanted to have some more useful tricks.
For exemple, when we see the selected tattos with a closer look, we would like to rotate the camera. Unfortunatly, every time I try, the camera is still stucked.
There may be something I still don’t understand. Thus, I seek some help
Here’s my code to help you see the problem
ESX = nil
local currentTattoos, cam, CurrentActionData = {}, -1, {}
local HasAlreadyEnteredMarker, CurrentAction, CurrentActionMsg
local isCamFixed = false
local playerHasBeenUndressed = false
local playerSex = -1
local zoomOffset, camOffset, heading = 0.4, -0.6, 90.0
local selectedX, selectedY, selectedZ, selectedRotZ = 0.0, 0.0, 0.0, 0.0
local selectedCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
--[[ ________________________________
All threads
_____________________________________ ]]--
--[[ share object. What does it do precisely ? ]]--
Citizen.CreateThread(function()
while ESX == nil do
TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
Citizen.Wait(0)
end
end)
--[[ Key controls ]]--
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if CurrentAction then
ESX.ShowHelpNotification(CurrentActionMsg)
if IsControlJustReleased(0, 38) then
if CurrentAction == 'tattoo_shop' then
OpenTattooShopMenu(false)
end
CurrentAction = nil
end
else
Citizen.Wait(500)
end
end
end)
--[[ Fix camera ]]--
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if isCamFixed then
DisableAllControlActions(0)
EnableControlAction(0, 109, true) -- 6 NUMPAD
EnableControlAction(0, 108, true) -- 4 NUMPAD
EnableControlAction(0, 172, true) -- UP ARROW
EnableControlAction(0, 173, true) -- DOWN ARROW
EnableControlAction(0, 174, true) -- LEFT ARROW
EnableControlAction(0, 175, true) -- RIGHT ARROW
EnableControlAction(0, 191, true) -- ENTER
--EnableControlAction(0, 215, true) -- ENTER
--EnableControlAction(0, 18, true) -- ENTER
EnableControlAction(0, 177, true) --BackSpace, Esc, Right MB
local angle = heading * math.pi / 180.0
local coords = GetEntityCoords(playerPed)
local theta = {
x = math.cos(angle),
y = math.sin(angle)
}
local pos = {
x = coords.x + (zoomOffset * theta.x),
y = coords.y + (zoomOffset * theta.y)
}
local angleToLook = heading - 140.0
if angleToLook > 360 then
angleToLook = angleToLook - 360
elseif angleToLook < 0 then
angleToLook = angleToLook + 360
end
angleToLook = angleToLook * math.pi / 180.0
local thetaToLook = {
x = math.cos(angleToLook),
y = math.sin(angleToLook)
}
SetCamCoord(cam, x + selectedX, y + selectedY, selectedZ)
SetCamRot(cam, 0.0, 0.0, selectedRotZ)
ESX.ShowHelpNotification(_U('use_rotate_view'))
else
Citizen.Wait(200)
end
end
end)
--[[ Being able to rotate camera in isCamFixed ]]--
Citizen.CreateThread(function()
local angle = 250
while true do
Citizen.Wait(0)
if isCamFixed then
if IsControlPressed(0, 108) then
print('je presse 4')
angle = angle - 1
elseif IsControlPressed(0, 109) then
angle = angle + 1
end
if angle > 360 then
angle = angle - 360
elseif angle < 0 then
angle = angle + 360
end
heading = angle + 0.0
else
Citizen.Wait(500)
end
end
end)
--[[ Sound and text ]]--
Citizen.CreateThread(function()
for k,v in pairs(Config.Zones) do
local blip = AddBlipForCoord(v)
SetBlipSprite(blip, 75)
SetBlipColour(blip, 1)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName('STRING')
AddTextComponentString(_U('tattoo_shop'))
EndTextCommandSetBlipName(blip)
end
end)
--[[ Display markers ]]--
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
local coords, letSleep = GetEntityCoords(PlayerPedId()), true
for k,v in pairs(Config.Zones) do
if (Config.Type ~= -1 and GetDistanceBetweenCoords(coords, v, true) < Config.DrawDistance) then
DrawMarker(Config.Type, v, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Config.Size.x, Config.Size.y, Config.Size.z, Config.Color.r, Config.Color.g, Config.Color.b, 100, false, true, 2, false, false, false, false)
letSleep = false
end
end
if letSleep then
Citizen.Wait(500)
end
end
end)
--[[ Enter / Exit marker events ]]--
Citizen.CreateThread(function()
while true do
Citizen.Wait(100)
local coords = GetEntityCoords(PlayerPedId())
local isInMarker = false
local currentZone, LastZone
for k,v in pairs(Config.Zones) do
if GetDistanceBetweenCoords(coords, v, true) < Config.Size.x then
isInMarker = true
currentZone = 'TattooShop'
LastZone = 'TattooShop'
end
end
if isInMarker and not HasAlreadyEnteredMarker then
HasAlreadyEnteredMarker = true
TriggerEvent('ev_tattooshops:hasEnteredMarker', currentZone)
end
if not isInMarker and HasAlreadyEnteredMarker then
HasAlreadyEnteredMarker = false
TriggerEvent('ev_tattooshops:hasExitedMarker', LastZone)
end
end
end)
--[[ ________________________________
All EventHandler
_____________________________________ ]]--
--[[ While loading player skin ]]--
AddEventHandler('skinchanger:modelLoaded', function()
ESX.TriggerServerCallback('ev_tattooshops:requestPlayerTattoos', function(tattooList)
-- get and set playerSex
TriggerEvent('skinchanger:getSkin', function(skin)
playerSex = skin.sex
end)
if tattooList then
for k,v in pairs(tattooList) do
if playerSex == 0 then
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashM))
else
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashF))
end
end
currentTattoos = tattooList
end
end)
end)
--[[ Event to open tattoos' shop ]]--
RegisterNetEvent('ev_tattooshops:openTattooMenu')
AddEventHandler('ev_tattooshops:openTattooMenu', function(isRemovingByEMS, target)
ESX.TriggerServerCallback('ev_tattooshops:requestTattoosOfPlayer', function(tattooList)
if tattooList then
OpenTattooShopMenu(isRemovingByEMS, tattooList, target)
end
end, target)
end)
--[[ Event when player enters to the marker of tattoos' shop ]]--
AddEventHandler('ev_tattooshops:hasEnteredMarker', function(zone)
if zone == 'TattooShop' then
CurrentAction = 'tattoo_shop'
CurrentActionMsg = _U('tattoo_shop_prompt')
CurrentActionData = {zone = zone}
end
end)
--[[ Event when player exits to the marker of tattoos' shop ]]--
AddEventHandler('ev_tattooshops:hasExitedMarker', function(zone)
CurrentAction = nil
ESX.UI.Menu.CloseAll()
if playerHasBeenUndressed then
cleanPlayer()
setPedSkin()
playerHasBeenUndressed = false
end
end)
--[[ Event when player remove a tattoo ]]--
RegisterNetEvent('ev_tattooshops:removeTattoo')
AddEventHandler('ev_tattooshops:removeTattoo', function(tattoo)
for i, aTattoo in ipairs(currentTattoos) do
if aTattoo.collection == tattoo.collection and aTattoo.texture == tattoo.texture then
table.remove(currentTattoos, i)
end
end
cleanPlayer()
end)
--[[ ________________________________
All menu function
_____________________________________ ]]--
-- [[ Function to open tattoos' shop and set player naked]]--
function OpenTattooShopMenu(isRemovingByEMS, listTattoos, target)
local elements = {}
if isRemovingByEMS then
currentTattoos = listTattoos
end
-- create all categories setted in tatooList --
for k,v in pairs(Config.TattooCategories) do
local label = _U('category', v.name)
if v.new then
label = _U('category_new', v.name)
end
table.insert(elements, {label= label, value = v.value})
end
-- see if delete it will do something --
if DoesCamExist(cam) then
RenderScriptCams(false, false, 0, 1, 0)
DestroyCam(cam, false)
end
-- put player naked
TriggerEvent('skinchanger:getSkin', function(skin)
-- I do prefer to confirm sex here, even if we got it previously
playerSex = skin.sex
if skin.sex == 0 then
TriggerEvent('skinchanger:loadClothes', skin, {
['torso_1'] = 15,
['torso_2'] = 0,
['tshirt_1'] = 15,
['tshirt_2'] = 0,
['arms'] = 15,
['arms_2'] = 0,
['decals_1'] = 0,
['decals_2'] = 0,
['bags_1'] = 0,
['bags_2'] = 0,
['pants_1'] = 61,
['pants_2'] = 1,
['bags_1'] = 0,
['bags_2'] = 0,
['shoes_1'] = 34,
['shoes_2'] = 0,
})
else
TriggerEvent('skinchanger:loadClothes', skin, {
['torso_1'] = 15,
['torso_2'] = 0,
['tshirt_1'] = 15,
['tshirt_2'] = 0,
['arms'] = 15,
['arms_2'] = 0,
['decals_1'] = 0,
['decals_2'] = 0,
['bags_1'] = 0,
['bags_2'] = 0,
['pants_1'] = 15,
['pants_2'] = 0,
['bags_1'] = 0,
['bags_2'] = 0,
['shoes_1'] = 35,
['shoes_2'] = 0,
})
end
end)
tattooCategoriesMenu(isRemovingByEMS, listTattoos, target, elements, currentTattoos)
end
-- [[ Function to manage all actions in TattooCategoryMenu (where you have all listed tattoos' categories)]]--
function tattooCategoriesMenu(isRemovingByEMS, listTattoos, target, elements, currentTattoos)
-- open window's menu with tattoos' categories
ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'tattoo_shop', {
title = _U('tattoos'),
align = 'top-left',
elements = elements
}, function(category, categoryMenu)
local currentLabel, currentValue = category.current.label, category.current.value
if category.current.value then
elements = {{label = _U('go_back_to_menu'), value = nil}}
for k,v in pairs(Config.TattooList[category.current.value]) do
local hasAlreadyTattoo = false
local putTattooInList = false
if playerSex == 0 and v.nameHashM ~= "" then
putTattooInList = true
elseif playerSex == 1 and v.nameHashF ~= "" then
putTattooInList = true
end
for i, aTattoo in pairs(currentTattoos) do
if aTattoo.collection == currentValue and aTattoo.texture == k then
hasAlreadyTattoo = true
end
end
if putTattooInList then
if not isRemovingByEMS then
if hasAlreadyTattoo then
local label = _U('already_have_tattoo_item', v.name)
if v.new then
label = _U('already_have_tattoo_item_new', v.name)
end
table.insert(elements, {
--label = _U('erase_tattoo_item', k, _U('money_amount', ESX.Math.GroupDigits(v.price))),
label = _U('already_have_tattoo_item', v.name),
value = k,
price = v.price,
toRemove = true
})
else
local label = _U('tattoo_item', v.name, _U('money_amount', ESX.Math.GroupDigits(v.price)))
if v.new then
label = _U('tattoo_item_new', v.name, _U('money_amount', ESX.Math.GroupDigits(v.price)))
end
table.insert(elements, {
label = label,
value = k,
price = v.price,
toRemove = false
})
end
else
if hasAlreadyTattoo then
table.insert(elements, {
label = _U('ems_remove_tattoo_item', v.name),
value = k
})
end
end
end
end
tattooListMenu(isRemovingByEMS, listTattoos, target, elements, currentLabel, currentValue, currentTattoos)
end
end, function(category, categoryMenu)
categoryMenu.close()
if not isRemovingByEMS then
setPedSkin()
end
end)
end
--[[ Function to display tattoos' list according to selected category ]]--
function tattooListMenu(isRemovingByEMS, listTattoos, target, elements, currentLabel, currentValue, currentTattoos)
-- open window's menu with tattoos from the selected categorie
ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'tattoo_shop_categories', {
title = _U('tattoos') .. ' | '..currentLabel,
align = 'top-left',
elements = elements
}, function(data2, tattooListCategoryMenu)
if data2.current.value ~= nil then
local tattoo = {collection = currentValue, texture = data2.current.value}
if not isRemovingByEMS then
local price = data2.current.price
if data2.current.toRemove then
--[[ESX.TriggerServerCallback('ev_tattooshops:eraseTattoo', function(success)
if success then
for i, aTattoo in ipairs(currentTattoos) do
if aTattoo.collection == tattoo.collection and aTattoo.texture == tattoo.texture then
table.remove(currentTattoos,i)
end
end
cleanPlayer()
end
end, currentTattoos, price, tattoo)]]--
TriggerEvent('esx:showNotification', _U('already_have_tattoo'))
else
-- voir si c'est encore utile de le garder ou pas --
RenderScriptCams(false, false, 0, 1, 0)
DestroyCam(cam, false)
confirmSelectedTattooMenu(currentTattoos, tattoo, price)
end
else
ESX.TriggerServerCallback('ev_tattooshops:eraseTattooFromEMS', function()
for i, aTattoo in ipairs(currentTattoos) do
if aTattoo.collection == tattoo.collection and aTattoo.texture == tattoo.texture then
table.remove(currentTattoos,i)
end
end
tattooListCategoryMenu.close()
end, currentTattoos, tattoo, target)
end
else
-- we go on return. So we must delete temporary tattoo. Can be used if player move outside tattoo's shop marker or if pressed escap button
cleanPlayer()
tattooListCategoryMenu.close()
end
end, function(data2, tattooListCategoryMenu)
if not isRemovingByEMS then
isCamFixed = false
FreezeEntityPosition(GetPlayerPed(-1), false)
cleanPlayer()
tattooListCategoryMenu.close()
RenderScriptCams(false, false, 0, 1, 0)
DestroyCam(cam, false)
--setPedSkin()
else
cleanPlayer()
tattooListCategoryMenu.close()
end
end, function(data2, tattooListCategoryMenu) -- when highlighted
if not isRemovingByEMS then
if data2.current.value ~= nil then
FreezeEntityPosition(GetPlayerPed(-1), false)
RenderScriptCams(false, false, 0, 1, 0)
drawTattoo(data2.current.value, currentValue)
playerHasBeenUndressed = true
else
isCamFixed = false
-- no need to display last highlighted tattoo. Can be confusing for players
cleanPlayer()
FreezeEntityPosition(GetPlayerPed(-1), false)
RenderScriptCams(false, false, 0, 1, 0)
DestroyCam(cam, false)
end
end
end)
end
--[[ Function to display tattoos' list according to selected category ]]--
function confirmSelectedTattooMenu(currentTattoos, tattoo, price)
-- confirm tattoo
ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'tattoo_confirm', {
title = _U('tattoo_confirm'),
align = 'top-left',
elements = {
{label = _U('yes'), value = 'yes'},
{label = _U('no'), value = 'no'}
}
}, function(data3, confirmTattooMenu)
if data3.current.value == 'yes' then
ESX.TriggerServerCallback('ev_tattooshops:purchaseTattoo', function(success)
if success then
table.insert(currentTattoos, tattoo)
cleanPlayer()
end
confirmTattooMenu.close()
FreezeEntityPosition(GetPlayerPed(-1), false)
cleanPlayer()
end, currentTattoos, price, tattoo)
elseif data3.current.value == 'no' then
confirmTattooMenu.close()
end
end, function(data3, confirmTattooMenu)
confirmTattooMenu.close()
cleanPlayer()
end)
end
--[[ todo : function to add more opacity (put more than one same tattoo)]]--
--[[ ________________________________
All functions
_____________________________________ ]]--
--[[ draw tattoo on player for display purposes ]]--
function drawTattoo(current, collection)
SetEntityHeading(PlayerPedId(), 297.7296)
ClearPedDecorations(PlayerPedId())
-- tattoos already on player
for k,v in pairs(currentTattoos) do
if playerSex == 0 then
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashM))
else
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashF))
end
end
-- new tattoo to display (not already bought)
if playerSex == 0 then
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[collection][current].collection), GetHashKey(Config.TattooList[collection][current].nameHashM))
else
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[collection][current].collection), GetHashKey(Config.TattooList[collection][current].nameHashF))
end
if not DoesCamExist(cam) then
cam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(cam, GetEntityCoords(PlayerPedId()))
SetCamRot(cam, 0.0, 0.0, 0.0)
SetCamActive(cam, true)
RenderScriptCams(true, false, 0, true, true)
SetCamCoord(cam, GetEntityCoords(PlayerPedId()))
end
local x,y,z = table.unpack(GetEntityCoords(PlayerPedId()))
selectedX, selectedY, selectedZ = x + Config.TattooList[collection][current].addedX, y + Config.TattooList[collection][current].addedY, z + Config.TattooList[collection][current].addedZ
selectedRotZ = Config.TattooList[collection][current].rotZ
selectedCam = cam
isCamFixed = true
--[[
SetCamCoord(cam, x + Config.TattooList[collection][current].addedX, y + Config.TattooList[collection][current].addedY, z + Config.TattooList[collection][current].addedZ)
SetCamRot(cam, 0.0, 0.0, Config.TattooList[collection][current].rotZ)
]]--
end
--[[ clean player from all and draws its tattoos ]]--
function cleanPlayer()
ClearPedDecorations(PlayerPedId())
for k,v in pairs(currentTattoos) do
if playerSex == 0 then
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashM))
else
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashF))
end
end
end
--[[ set player skin ]]--
function setPedSkin()
ESX.TriggerServerCallback('esx_skin:getPlayerSkin', function(skin)
TriggerEvent('skinchanger:loadSkin', skin)
end)
Citizen.Wait(1000)
for k,v in pairs(currentTattoos) do
if playerSex == 0 then
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashM))
else
AddPedDecorationFromHashes(PlayerPedId(), GetHashKey(Config.TattooList[v.collection][v.texture].collection), GetHashKey(Config.TattooList[v.collection][v.texture].nameHashF))
end
end
end
And here an example of data used in tattoos.json
Config.TattooList = {
test_overlays = {
{
-- mettre la cam au centre du perso x était à 2.0
name = 'Grim Rider',
nameHashM = 'MP_MP_Biker_Tat_042_M',
nameHashF = 'MP_MP_Biker_Tat_042_F',
addedX = 0.4,
addedY=-0.7,
addedZ=0.1,
rotZ = 24.3,
price = 1350,
new = false,
collection = "mpbiker_overlays"
},
{
-- test
name = 'Grim Rider',
nameHashM = 'MP_MP_Biker_Tat_042_M',
nameHashF = 'MP_MP_Biker_Tat_042_F',
addedX = 0.4,
addedY=0.0,
addedZ=0.1,
rotZ = 24.3,
price = 1350,
new = false,
collection = "mpbiker_overlays"
},
{
-- tourner la camera
name = 'Scorched Soul',
nameHashM = 'MP_MP_Biker_Tat_037_M',
nameHashF = 'MP_MP_Biker_Tat_037_F',
addedX = 0.1,
addedY=1.0,
addedZ=-0.2,
rotZ = 156.7,
price = 1200,
new = false,
collection = "mpbiker_overlays"
},
{
-- pouvoir tourner la cam vers la droite
name = 'Ride Free',
nameHashM = 'MP_MP_Biker_Tat_044_M',
nameHashF = 'MP_MP_Biker_Tat_044_F',
addedX = 0.1,
addedY=0.9,
addedZ=-0.2,
rotZ = 156.7,
price = 1200,
new = false,
collection = "mpbiker_overlays"
},
},
}
Config.TattooCategories = {
{name = _U('test'), value = 'test_overlays', new = false},
}
I know my problem is about this line because if I don’t use it, the camera is not glued. The camera will not be at the place I want it to be :
SetCamCoord(cam, x + selectedX, y + selectedY, selectedZ)
And when I press 4 or 6 (there is a client trace when I press 4 for testing purpose), the camera does not rotate.
Someone can please tell me why I do have this issue and how to resolve it ?
Thank you by advance