2026-04-14 17:41:39 +02:00

567 lines
19 KiB
Lua

local isJustPressed = IsDisabledControlJustPressed
local isHeld = IsDisabledControlPressed
local drawLine = DrawLine
local drawPoly = DrawPoly
local actionControls = ActionControls
creator = {}
creator.raycast = {
flags = { ped = 17, vehicle = 17, object = 1 },
defaults = {
models = { ped = "mp_m_shopkeep_01", vehicle = "t20", object = "prop_paper_bag_01" }
},
entities = { ped = 1, vehicle = 2, object = 3 },
minPointLength = Config.MinPointLength,
height = 25.0,
points = {}
}
function creator.updateUI(self)
if not self.visible then return end
local creatorGarages = {}
for _, garageData in pairs(Config.Garages) do
if garageData.creator then
creatorGarages[#creatorGarages + 1] = garageData
end
end
local gang = GetGang()
SendReactMessage("toggle_creator", {
visible = true,
data = {
garages = creatorGarages,
jobs = self.jobs,
job = GetJobName(),
gang = (gang and gang.name) or ""
}
})
end
function creator.open(self)
if raycast.active then
return Notification(i18n.t("raycast.must_be_completed"), "error")
end
if not self.jobs then
local data = lib.callback.await("garages:getCreatorData", false)
if not data then
return Error("creator:open", "No data returned from server. Did you follow the docs?", data)
end
self.jobs = data.jobs
end
ToggleHud(false)
self.visible = true
SetNuiFocus(true, true)
self:updateUI()
Debug("Creator opened")
end
function creator.close(self)
if not self.visible then return end
self.visible = false
ToggleHud(true)
SendReactMessage("toggle_creator", { visible = false })
Debug("Creator closed")
end
RegisterCommand("garagecreator", function()
local hasPermission = lib.callback.await("garages:hasPermission", false)
if not hasPermission then
return Notification(i18n.t("no_permission"), "error")
end
creator:open()
end)
RegisterCommand("creategarage", function()
print("[GARAGES] Commande /creategarage invoquee")
ExecuteCommand("garagecreator")
end)
print("[GARAGES] Alias /creategarage enregistre avec succes !")
function creator.getPointLength(self, points)
local totalLength = 0.0
for i = 1, #points do
local nextPoint = points[i + 1] or points[1]
totalLength = totalLength + #(points[i] - nextPoint)
end
return totalLength
end
function creator.drawRectangle(self, corners, allPoints)
local totalLength = self:getPointLength(allPoints)
local isComplete = #allPoints >= 4
local r = isComplete and 255 or 255
local g = isComplete and 255 or 40
local b = isComplete and 0 or 24
local function poly(p1, p2, p3)
drawPoly(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, r, g, b, 100)
end
poly(corners[1], corners[2], corners[3])
poly(corners[2], corners[1], corners[3])
poly(corners[1], corners[4], corners[3])
poly(corners[4], corners[1], corners[3])
end
function creator.drawLines(self)
local height = self.raycast.height
local halfHeight = vec(0, 0, height / 2)
for i = 1, #self.raycast.points do
local currentPoint = self.raycast.points[i]
local z = self.raycast.zCoords or currentPoint.z
self.raycast.points[i] = vec(currentPoint.x, currentPoint.y, z)
local topPoint = self.raycast.points[i] + halfHeight
local bottomPoint = self.raycast.points[i] - halfHeight
local nextIdx = i + 1
local nextPoint = self.raycast.points[nextIdx] or self.raycast.points[1]
local nextTop = nextPoint + halfHeight
local nextBottom = nextPoint - halfHeight
local function line(a, b)
drawLine(a.x, a.y, a.z, b.x, b.y, b.z, 255, 42, 24, 225)
end
line(topPoint, bottomPoint)
line(topPoint, nextTop)
line(bottomPoint, nextBottom)
line(currentPoint, nextPoint)
self:drawRectangle({ topPoint, bottomPoint, nextBottom, nextTop }, self.raycast.points)
end
end
function creator.isPointInAnyZone(self, coords)
for _, zone in pairs(PolyZones) do
if zone:contains(coords) then
return true
end
end
return false
end
function creator.raycastRectangle(self)
self.raycast.points = {}
local pedCoords = GetEntityCoords(cache.ped)
self.raycast.zCoords = math.round(pedCoords.z) + 0.0
self.raycast.height = 25.0
actionControls.leftClick.label = i18n.t("creator.raycast.add_point")
actionControls.rotate_z_scroll.label = i18n.t("creator.raycast.point_size")
Notification(i18n.t("creator.raycast.info"), "info")
raycast:freeCamera(function(camData)
creator:drawLines()
if isJustPressed(0, actionControls.cancel.codes[1]) then
camData:destroy()
end
if isJustPressed(0, actionControls.done.codes[1]) then
if not creator.raycast.points then
Notification(i18n.t("creator.raycast.no_point_selected"), "error")
else
camData:destroy()
end
end
if isJustPressed(0, actionControls.leftClick.codes[1]) and camData.hit then
if creator:isPointInAnyZone(camData.coords) then
Notification(i18n.t("creator.raycast.point_in_another_zone"), "error")
else
local pts = creator.raycast.points
pts[#pts + 1] = vec3(camData.coords.x, camData.coords.y, camData.coords.z)
end
end
if isJustPressed(0, actionControls.undo_point.codes[1]) then
local pts = creator.raycast.points
if #pts > 0 then
pts[#pts] = nil
end
end
if isHeld(0, actionControls.boundary_height.codes[1]) then
creator.raycast.height = creator.raycast.height + 15.0 * GetFrameTime()
elseif isHeld(0, actionControls.boundary_height.codes[2]) then
creator.raycast.height = creator.raycast.height - 15.0 * GetFrameTime()
end
end, { "done", "undo_point", "leftClick", "cancel", "boundary_height" })
creator.raycast.zCoords = nil
if #creator.raycast.points < 4 then
return nil
end
return {
points = creator.raycast.points,
thickness = creator.raycast.height
}
end
function creator.isInPoints(self, points, hitEntity)
local totalLength = self:getPointLength(points)
if #points < 3 then return false end
local minZ = points[1].z
local maxZ = points[1].z
local halfHeight = creator.raycast.height / 2.4
for i = 2, #points do
if minZ > points[i].z then minZ = points[i].z end
if maxZ < points[i].z then maxZ = points[i].z end
end
if hitEntity.z < (minZ - halfHeight) or hitEntity.z > (maxZ + halfHeight) then
return false
end
local x = hitEntity.x
local y = hitEntity.y
local inside = false
for i = 1, #points do
local j = (i % #points) + 1
local xi = points[i].x
local yi = points[i].y
local xj = points[j].x
local yj = points[j].y
if (yi < y) ~= (yj < y) then
local intersectX = (xj - xi) * (y - yi) / (yj - yi) + xi
if x < intersectX then
inside = not inside
end
end
end
return inside
end
function creator.selectPoint(self, entityType, count, options)
if #self.raycast.points == 0 then
if not (options and options.disablePoints) then
return Notification(i18n.t("creator.no_points_selected"), "error")
end
end
if not entityType then entityType = "empty" end
if not count then count = 1 end
if not options then options = {} end
local selectedPoints = {}
local heading = 0
local raycastFlags = self.raycast.flags[entityType]
local entityHandle = nil
options.points = options.points or {}
if entityType ~= "empty" then
options.model = options.model or self.raycast.defaults.models[entityType]
lib.requestModel(options.model)
end
local function spawnPreviewEntity(model, coords)
if not model or entityType == "empty" then return nil end
local entity = nil
if entityType == "ped" then
entity = CreatePed(28, model, coords.x, coords.y, coords.z, coords.w or 0, false, false)
elseif entityType == "vehicle" then
entity = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w or 0, false, true)
elseif entityType == "object" then
entity = CreateObject(model, coords.x, coords.y, coords.z, false, false)
if coords.w then SetEntityHeading(entity, coords.w) end
end
if entity then
SetEntityAsMissionEntity(entity, true, true)
FreezeEntityPosition(entity, true)
SetEntityInvincible(entity, true)
end
return entity
end
entityHandle = spawnPreviewEntity(options.model and joaat(options.model))
for _, savedPoint in pairs(options.points) do
if savedPoint.model then
lib.requestModel(savedPoint.model)
local pointEntity = spawnPreviewEntity(joaat(savedPoint.model), savedPoint.coords)
savedPoint.handle = pointEntity
if pointEntity then
SetEntityDrawOutline(pointEntity, true)
if savedPoint.coords.w then
SetEntityHeading(pointEntity, savedPoint.coords.w)
end
end
end
end
actionControls.leftClick.label = i18n.t("creator.raycast.add_point")
actionControls.rotate_z.label = i18n.t("creator.raycast.rotate_z_scroll")
actionControls.rightClick.label = i18n.t("creator.raycast.undo")
local controls = { "leftClick", "rightClick" }
if entityType ~= "empty" then
controls[#controls + 1] = "rotate_z"
end
if options.pressEnterToSelect then
actionControls.done.label = i18n.t("creator.raycast.press_enter_to_select")
controls[#controls + 1] = "done"
controls[#controls + 1] = "rightClick"
end
raycast:gameplayCamera(function(camData)
DisableControlAction(0, 25, true)
DisableControlAction(0, 24, true)
DisableControlAction(0, 20, true)
DisableControlAction(0, 73, true)
DisableControlAction(0, 191, true)
for _, pt in pairs(options.points) do
DrawMarker(28, pt.coords.x, pt.coords.y, pt.coords.z,
0, 0, 0, 0, 0, 0, 0.2, 0.2, 0.2,
255, 42, 24, 100,
false, false, 0, true, false, false, false)
end
creator:drawLines()
if not camData.hit then return end
if entityHandle then
if camData.lastCoords ~= camData.coords then
SetEntityCoords(entityHandle, camData.coords.x, camData.coords.y, camData.coords.z, false, false, true)
end
end
if isJustPressed(0, 24) then
if not (options and options.disablePoints) then
if not creator:isInPoints(creator.raycast.points, camData.coords) then
return Notification(i18n.t("creator.raycast.not_in_points"), "error")
end
end
local clickCoords = vec3(camData.coords.x, camData.coords.y, camData.coords.z)
local newEntity = nil
if entityType ~= "empty" then
local clickVec4 = vec4(clickCoords.x, clickCoords.y, clickCoords.z, GetEntityHeading(entityHandle))
clickCoords = clickVec4
newEntity = spawnPreviewEntity(nil, clickVec4)
if newEntity then
SetEntityAlpha(newEntity, 170, false)
end
end
selectedPoints[#selectedPoints + 1] = { coords = clickCoords, handle = newEntity }
if not options.pressEnterToSelect then
if #selectedPoints == count then
Notification(i18n.t("creator.raycast.completed"), "info")
camData:destroy()
else
Notification(i18n.t("creator.raycast.selected_point", { count = #selectedPoints }), "info")
end
end
end
if options.pressEnterToSelect and isJustPressed(0, actionControls.done.codes[1]) then
Notification(i18n.t("creator.raycast.completed"), "info")
camData:destroy()
end
if isJustPressed(0, actionControls.rightClick.codes[1]) then
if #selectedPoints > 0 then
local lastEntry = selectedPoints[#selectedPoints]
if lastEntry and lastEntry.handle then
DeleteEntity(lastEntry.handle)
end
selectedPoints[#selectedPoints] = nil
end
end
if entityType ~= "empty" then
if isHeld(0, 20) then
heading = (heading + 1.0) % 360
SetEntityHeading(entityHandle, heading)
elseif isHeld(0, 73) then
heading = (heading - 1.0) % 360
SetEntityHeading(entityHandle, heading)
end
end
end, controls, raycastFlags)
for _, pt in pairs(selectedPoints) do
if pt.handle then DeleteEntity(pt.handle) end
end
for _, savedPt in pairs(options.points) do
if savedPt.model then
DeleteEntity(savedPt.handle)
SetModelAsNoLongerNeeded(savedPt.model)
end
end
Utils.RemoveInstructional()
if entityHandle then
DeleteEntity(entityHandle)
SetModelAsNoLongerNeeded(options.model)
end
return table.map(selectedPoints, function(pt) return pt.coords end)
end
RegisterCommand("t1", function()
print(json.encode(coords))
end)
function creator.selectEntity(self, entityType, count, options)
if #self.raycast.points == 0 then
return Notification(i18n.t("creator.no_points_selected"), "error")
end
assert(entityType, "creator:selectEntity ::: entityType is required")
if not count then count = 1 end
if not options then options = {} end
local selectedEntities = {}
local heading = 0
local currentZ = 0
local trackedEntity = nil
local currentHoveredEnt = nil
local pendingPedEntity = nil
options.disabledEntities = options.disabledEntities or {}
for _, disabledEnt in pairs(options.disabledEntities) do
SetEntityDrawOutline(disabledEnt, true)
end
if options.ped then
lib.requestModel(options.ped.model)
pendingPedEntity = CreatePed(28, options.ped.model, 0, 0, 0, 0, false, false)
SetEntityVisible(pendingPedEntity, false, false)
SetEntityInvincible(pendingPedEntity, true)
FreezeEntityPosition(pendingPedEntity, true)
SetEntityCompletelyDisableCollision(pendingPedEntity, true, false)
local anim = options.ped.anim
lib.requestAnimDict(anim.dict, 3000)
TaskPlayAnim(pendingPedEntity, anim.dict, anim.name, 8.0, 1.0, -1, 1, 0, false, false, false)
RemoveAnimDict(anim.dict)
options.disabledEntities[#options.disabledEntities + 1] = pendingPedEntity
local controls = { "leftClick" }
controls[#controls + 1] = "rotate_z"
controls[#controls + 1] = "offset_z"
end
Notification(i18n.t("creator.raycast.select_entity_info", { entityType = entityType }), "info")
raycast:gameplayCamera(function(camData)
DisableControlAction(0, actionControls.rotate_z.codes[1], true)
DisableControlAction(0, actionControls.rotate_z.codes[2], true)
DisableControlAction(0, actionControls.offset_z.codes[1], true)
DisableControlAction(0, actionControls.offset_z.codes[2], true)
creator:drawLines()
if pendingPedEntity then
Utils.DrawEntityBoundingBox(pendingPedEntity, 255, 42, 24, 100)
end
if currentHoveredEnt ~= camData.entity then
local isDisabled = table.contains(options.disabledEntities, currentHoveredEnt)
if not isDisabled then
SetEntityDrawOutline(currentHoveredEnt, false)
if pendingPedEntity then
SetEntityVisible(pendingPedEntity, false, false)
end
end
end
currentHoveredEnt = camData.entity
if camData.hit and camData.entity then
local isNewEntity = (currentHoveredEnt ~= camData.entity)
if isNewEntity then
local isDisabled = table.contains(options.disabledEntities, camData.entity)
if not isDisabled then
SetEntityDrawOutline(camData.entity, true)
if pendingPedEntity then
SetEntityVisible(pendingPedEntity, true, false)
end
end
end
if isJustPressed(0, 24) then
local isDisabled = table.contains(options.disabledEntities, camData.entity)
if not isDisabled then
local coords = vec4(
GetEntityCoords(camData.entity).x,
GetEntityCoords(camData.entity).y,
GetEntityCoords(camData.entity).z + currentZ,
GetEntityHeading(camData.entity)
)
selectedEntities[#selectedEntities + 1] = { entity = camData.entity, coords = coords }
if not options.pressEnterToSelect then
if #selectedEntities == count then
camData:destroy()
end
end
end
end
end
if options.pressEnterToSelect and isJustPressed(0, actionControls.done.codes[1]) then
camData:destroy()
end
if pendingPedEntity then
if isHeld(0, actionControls.rotate_z.codes[1]) then
heading = (heading + 1.0) % 360
SetEntityHeading(pendingPedEntity, heading)
elseif isHeld(0, actionControls.rotate_z.codes[2]) then
heading = (heading - 1.0) % 360
SetEntityHeading(pendingPedEntity, heading)
end
if isHeld(0, actionControls.offset_z.codes[1]) then
currentZ = currentZ + GetFrameTime()
elseif isHeld(0, actionControls.offset_z.codes[2]) then
currentZ = currentZ - GetFrameTime()
end
end
end, { "leftClick" }, self.raycast.flags[entityType])
Utils.RemoveInstructional()
if pendingPedEntity then
DeleteEntity(pendingPedEntity)
end
return table.map(selectedEntities, function(entry)
return {
entity = entry.entity,
coords = {
x = entry.coords.x, y = entry.coords.y,
z = entry.coords.z, w = entry.coords.w
}
}
end)
end