567 lines
19 KiB
Lua
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
|