diff --git a/interactionDUI/dui_source/src/style.scss b/interactionDUI/dui_source/src/style.scss index a77769e..29a94a5 100644 --- a/interactionDUI/dui_source/src/style.scss +++ b/interactionDUI/dui_source/src/style.scss @@ -224,7 +224,7 @@ body { .v-enter-active, .v-leave-active { - transition: opacity 0.5s ease, transform 0.2s ease; + transition: opacity 0.3s ease, transform 0.1s ease-in; } .v-enter-to { diff --git a/interactionMenu/config.shared.lua b/interactionMenu/config.shared.lua index d86303c..5bdc8a8 100644 --- a/interactionMenu/config.shared.lua +++ b/interactionMenu/config.shared.lua @@ -10,7 +10,6 @@ Config = {} -Config.framework = 'qb' Config.devMode = true Config.interactionAudio = { @@ -24,7 +23,23 @@ Config.interactionAudio = { } } +Config.intervals = { + detection = 400 +} + Config.features = { positionCorrection = true, - time_based_theme_switch = true + timeBasedTheme = true, + drawIndicator = { + active = true + } +} + +Config.icons = { + 'stove', + 'stove2', + 'glowingball', + 'box', + 'wrench', + 'vending' } diff --git a/interactionMenu/fxmanifest.lua b/interactionMenu/fxmanifest.lua index cb8271a..32ffe2c 100644 --- a/interactionMenu/fxmanifest.lua +++ b/interactionMenu/fxmanifest.lua @@ -29,16 +29,18 @@ client_script { '@PolyZone/ComboZone.lua', -- - 'lua/frameworks/qb/client.lua', + -- 'lua/frameworks/qb/client.lua', -- 'lua/client/util.lua', 'lua/client/3dDuiMaker.lua', 'lua/client/menuContainer.lua', 'lua/client/interact.lua', + 'lua/client/drawIndicator.lua', -- examples / tests - 'lua/examples/*.lua' + 'lua/examples/*.lua', + -- 'lua/examples/test.lua', } server_script { @@ -46,7 +48,7 @@ server_script { } files { - 'indicator.png' + 'lua/client/icons/*.*', } -- provide 'qb-target' diff --git a/interactionMenu/indicator.png b/interactionMenu/indicator.png deleted file mode 100644 index d639dfb..0000000 Binary files a/interactionMenu/indicator.png and /dev/null differ diff --git a/interactionMenu/lua/client/drawIndicator.lua b/interactionMenu/lua/client/drawIndicator.lua new file mode 100644 index 0000000..2db268b --- /dev/null +++ b/interactionMenu/lua/client/drawIndicator.lua @@ -0,0 +1,171 @@ +if not Config.features.drawIndicator.active then + return +end + +-- #region Show sprite while holding alt + +local SpatialHashGrid = Util.SpatialHashGrid +local isTargetSpritesActive = false +local isSpriteThreadRunning = false +local StateManager = Util.StateManager() +local grid_position = SpatialHashGrid:new('position', 100) +local visiblePoints = {} +local visiblePointCount = 0 + +CreateThread(function() + local txd = CreateRuntimeTxd('interaction_txd_indicator') + CreateRuntimeTextureFromImage(txd, 'indicator', "lua/client/icons/indicator.png") + for index, value in ipairs(Config.icons) do + CreateRuntimeTextureFromImage(txd, value, ("lua/client/icons/%s.png"):format(value)) + end +end) + +-- even tho we can set it using the menu's data i use a const white color +-- sprite colors +local red = 255 +local green = 255 +local blue = 255 +local alpha = 255 + +-- minimum and maximum scale factors for x and y +local minScaleX = 0.02 / 4 +local maxScaleX = minScaleX * 5 +local minScaleY = 0.035 / 4 +local maxScaleY = minScaleY * 5 + +-- Distance thresholds +local minDistance = 2.0 +local maxDistance = 20.0 + +-- Function to draw the sprite with scaling based on distance +local function drawSprite(p, player_position, icon) + if not p then return end + -- Calculate the distance between the player and the point + local distance = #(vec3(p.x, p.y, p.z) - player_position) + local clampedDistance = math.max(minDistance, math.min(maxDistance, distance)) + + -- Pre-calculate the scale factor range + local scaleRangeX = maxScaleX - minScaleX + local scaleRangeY = maxScaleY - minScaleY + local distanceRange = maxDistance - minDistance + local normalizedDistance = (clampedDistance - minDistance) / distanceRange + + -- Calculate the scale factors based on the clamped distance + local scaleX = minScaleX + scaleRangeX * (1 - normalizedDistance) + local scaleY = minScaleY + scaleRangeY * (1 - normalizedDistance) + + -- Set the draw origin to the point's coordinates + SetDrawOrigin(p.x, p.y, p.z, 0) + + -- Draw the sprite with the calculated scales + DrawSprite('interaction_txd_indicator', icon or 'indicator', 0, 0, scaleX, scaleY, 0, red, green, blue, alpha) + ClearDrawOrigin() +end + + +local function getNearbyObjects(coords) + local objects = GetGamePool('CObject') + local nearby = {} + local count = 0 + + for i = 1, #objects do + local object = objects[i] + + local objectCoords = GetEntityCoords(object) + local distance = #(coords - objectCoords) + + if distance < maxDistance then + local entity_type = GetEntityType(object) + local model = GetEntityModel(object) + + local menuType = Container.getMenuType { + model = model, + entity = object, + entityType = entity_type + } + + local menu = Container.getMenu(model, object, nil) + local id = StateManager.get('id') + local menuId + + if id then + menuId = StateManager.get('entityModel') .. "|" .. StateManager.get('id') + end + + if menuType ~= 1 and not menuId or menuId ~= menu.id then + count += 1 + + nearby[count] = { + object = object, + coords = objectCoords, + type = entity_type, + icon = menu and menu.icon + } + end + end + end + + return nearby +end + +local entities = {} + +local function StartSpriteThread() + if isSpriteThreadRunning then return end + isSpriteThreadRunning = true + local player = PlayerPedId() + local playerPosition = StateManager.get('playerPosition') + local currentMenu = StateManager.get('id') + local isActive = StateManager.get('active') + + CreateThread(function() + while isSpriteThreadRunning do + isActive = StateManager.get('active') + currentMenu = StateManager.get('id') + entities = getNearbyObjects(playerPosition) + local nearPoints, totalNearPoints = grid_position:queryRange(playerPosition, 20) + visiblePoints, visiblePointCount = Util.filterVisiblePointsWithinRange(playerPosition, nearPoints) + + Wait(1000) + end + end) + + CreateThread(function() + while isTargetSpritesActive do + playerPosition = GetEntityCoords(player) + + if visiblePointCount > 0 then + for _, value in ipairs(visiblePoints.inView) do + if value.id ~= currentMenu then + drawSprite(value.point, playerPosition, visiblePoints.closest.icon) + end + end + + if visiblePoints.closest.id ~= currentMenu and not isActive then + drawSprite(visiblePoints.closest.point, playerPosition, visiblePoints.closest.icon) + end + end + + for index, value in ipairs(entities) do + drawSprite(value.coords, playerPosition, value.icon) + end + + Wait(10) + end + isSpriteThreadRunning = false + end) +end + +RegisterCommand('+toggleTargetSprites', function() + isTargetSpritesActive = true + StartSpriteThread() +end, false) + +RegisterCommand('-toggleTargetSprites', function() + isTargetSpritesActive = false +end, false) + +RegisterKeyMapping('+toggleTargetSprites', 'Toggle Target Sprites', 'keyboard', 'LMENU') +RegisterKeyMapping('~!+toggleTargetSprites', 'Toggle Target Sprites - Alternate Key', 'keyboard', 'RMENU') + +-- #endregion diff --git a/interactionMenu/lua/client/icons/box.png b/interactionMenu/lua/client/icons/box.png new file mode 100644 index 0000000..b2a8a4a Binary files /dev/null and b/interactionMenu/lua/client/icons/box.png differ diff --git a/interactionMenu/lua/client/icons/glowingball.png b/interactionMenu/lua/client/icons/glowingball.png new file mode 100644 index 0000000..e5af927 Binary files /dev/null and b/interactionMenu/lua/client/icons/glowingball.png differ diff --git a/interactionMenu/lua/client/icons/indicator.png b/interactionMenu/lua/client/icons/indicator.png new file mode 100644 index 0000000..c3d3e94 Binary files /dev/null and b/interactionMenu/lua/client/icons/indicator.png differ diff --git a/interactionMenu/lua/client/icons/stove.png b/interactionMenu/lua/client/icons/stove.png new file mode 100644 index 0000000..64ea519 Binary files /dev/null and b/interactionMenu/lua/client/icons/stove.png differ diff --git a/interactionMenu/lua/client/icons/stove2.png b/interactionMenu/lua/client/icons/stove2.png new file mode 100644 index 0000000..7936e87 Binary files /dev/null and b/interactionMenu/lua/client/icons/stove2.png differ diff --git a/interactionMenu/lua/client/icons/vending.png b/interactionMenu/lua/client/icons/vending.png new file mode 100644 index 0000000..20ff920 Binary files /dev/null and b/interactionMenu/lua/client/icons/vending.png differ diff --git a/interactionMenu/lua/client/icons/wrench.png b/interactionMenu/lua/client/icons/wrench.png new file mode 100644 index 0000000..04a9451 Binary files /dev/null and b/interactionMenu/lua/client/icons/wrench.png differ diff --git a/interactionMenu/lua/client/interact.lua b/interactionMenu/lua/client/interact.lua index af5e862..da6ff5d 100644 --- a/interactionMenu/lua/client/interact.lua +++ b/interactionMenu/lua/client/interact.lua @@ -34,10 +34,7 @@ local grid_zone = SpatialHashGrid:new('zone', 100) local grid_position = SpatialHashGrid:new('position', 100) local visiblePoints = {} -local visiblePointCount = 0 -- Render -local isTargetSpritesActive = false -local isSpriteThreadRunning = false local PersistentData = Util.PersistentData() local StateManager = Util.StateManager() @@ -201,97 +198,13 @@ local triggers = { end } --- #region Show sprite while holding alt -CreateThread(function() - local txd = CreateRuntimeTxd('interaction_txd_indicator') - CreateRuntimeTextureFromImage(txd, 'indicator', "indicator.png") -end) - -local function drawSprite(p) - if not p then return end - SetDrawOrigin(p.x, p.y, p.z, 0) - DrawSprite('interaction_txd_indicator', 'indicator', 0, 0, 0.02, 0.035, 0, 255, 255, 255, 255) - ClearDrawOrigin() -end - --- local function getNearbyObjects(coords, maxDistance) --- local objects = GetGamePool('CObject') --- local nearby = {} --- local count = 0 --- maxDistance = maxDistance or 2.0 - --- for i = 1, #objects do --- local object = objects[i] - --- local objectCoords = GetEntityCoords(object) --- local distance = #(coords - objectCoords) - --- if distance < maxDistance then --- count += 1 --- nearby[count] = { --- object = object, --- coords = objectCoords --- } --- end --- end - --- return nearby --- end - --- CreateThread(function() --- if not waitForScaleform() then return end - --- while true do --- local entities = getNearbyObjects(StateManager.get('playerPosition'), 10) - --- for index, value in ipairs(entities) do --- drawSprite(value.coords) --- end --- Wait(10) --- end --- end) - -local function StartSpriteThread() - if isSpriteThreadRunning then return end - isSpriteThreadRunning = true - - CreateThread(function() - while isTargetSpritesActive do - if visiblePointCount > 0 then - for _, value in ipairs(visiblePoints.inView) do - drawSprite(value.point) - end - - if visiblePoints.closest.id ~= StateManager.get('id') and not StateManager.get('active') then - drawSprite(visiblePoints.closest.point) - end - end - - Wait(10) - end - isSpriteThreadRunning = false - end) -end - -RegisterCommand('+toggleTargetSprites', function() - isTargetSpritesActive = true - StartSpriteThread() -end, false) - -RegisterCommand('-toggleTargetSprites', function() - isTargetSpritesActive = false -end, false) - -RegisterKeyMapping('+toggleTargetSprites', 'Toggle Target Sprites', 'keyboard', 'LMENU') -RegisterKeyMapping('~!+toggleTargetSprites', 'Toggle Target Sprites - Alternate Key', 'keyboard', 'RMENU') - --- #endregion - -- #region process data -- detect the menu type and set menu data and pass rest to render thread local function handlePositionBasedInteraction() - if visiblePoints.closest.distance and visiblePoints.closest.distance < 3 then + local maxDistance = visiblePoints.closest.maxDistance or 3 + + if visiblePoints.closest.distance and visiblePoints.closest.distance < maxDistance then StateManager.set('id', visiblePoints.closest.id) StateManager.set('menuType', MenuTypes['ON_POSITION']) StateManager.set('playerDistance', visiblePoints.closest.distance) @@ -326,7 +239,7 @@ local function waitForScaleform() end local function findClosestZone(playerPosition, range) - local zonesInRange = grid_zone:queryRange(playerPosition, 100) + local zonesInRange = grid_zone:queryRange(playerPosition, 25) for index, value in ipairs(zonesInRange) do if Container.zones[value.id] and Container.zones[value.id]:isPointInside(playerPosition) then @@ -340,14 +253,13 @@ end CreateThread(function() if not waitForScaleform() then return end -- We can bump it up to 1000 for better performance, but it looks better with 500/600 ms - local interval = 600 + local interval = Config.intervals.detection or 500 local pid = PlayerId() - -- give client sometime to actually load + -- give client sometime to load repeat Wait(1000) until NetworkIsPlayerActive(pid) == 1 - Wait(500) while true do local playerPed = PlayerPedId() @@ -382,12 +294,12 @@ CreateThread(function() playerPosition = playerPosition }, true, true) - local nearPoints, totalNearPoints = grid_position:queryRange(playerPosition, 100) - visiblePoints, visiblePointCount = Util.filterVisiblePointsWithinRange(playerPosition, nearPoints) + local nearPoints = grid_position:queryRange(playerPosition, 25) + visiblePoints = Util.filterVisiblePointsWithinRange(playerPosition, nearPoints) -- onZone - local closestZoneMenuId = findClosestZone(playerPosition) + local closestZoneMenuId = findClosestZone(playerPosition) - local menuType = Container.getMenuType { + local menuType = Container.getMenuType { model = model, entity = entity, entityType = entityType, @@ -483,6 +395,7 @@ end function Render.onPosition(currentMenuId) local data = Container.getMenu(nil, nil, currentMenuId) if not data then return end + -- print(data.flags.deleted) local persistentData = PersistentData.get(currentMenuId) if not canInteract(data, nil) then return end diff --git a/interactionMenu/lua/client/menuContainer.lua b/interactionMenu/lua/client/menuContainer.lua index a9eb19a..6c0b60b 100644 --- a/interactionMenu/lua/client/menuContainer.lua +++ b/interactionMenu/lua/client/menuContainer.lua @@ -45,6 +45,7 @@ Container = { bones = {}, globals = { bones = {}, + objects = {}, entities = {}, peds = {}, players = {}, @@ -252,6 +253,7 @@ function Container.create(t) glow = t.glow, extra = t.extra or {}, indicator = t.indicator, + icon = t.icon, interactions = {}, options = {}, metadata = { @@ -271,7 +273,7 @@ function Container.create(t) if t.position and not t.zone then instance.type = 'position' - instance.position = { x = t.position.x, y = t.position.y, z = t.position.z, id = id } + instance.position = { x = t.position.x, y = t.position.y, z = t.position.z, id = id, maxDistance = t.maxDistance } instance.rotation = t.rotation grid:insert(instance.position) @@ -502,6 +504,7 @@ local function populateMenus(container, combinedIds, id, bones, closestBoneName, container.static = data.flags and data.flags.static container.zone = data.zone container.rotation = data.rotation + container.icon = data.icon if data.flags.suppressGlobals then container.selected = {} @@ -1033,7 +1036,7 @@ function Container.syncData(scaleform, menuData, refreshUI) Container.validateAndSyncSelected(scaleform, menuData) end - if Config.features.time_based_theme_switch and is_daytime() ~= previous_daytime then + if Config.features.timeBasedTheme and is_daytime() ~= previous_daytime then previous_daytime = is_daytime() Interact:SetDarkMode(previous_daytime) end @@ -1165,6 +1168,10 @@ function Container.removeByInvokingResource(i_r) for key, menu in pairs(Container.data) do if menu.metadata.invokingResource == i_r then Container.data[key].flags.deleted = true + + if Container.data[key].type == 'position' then + grid:remove(Container.data[key].position) + end end end diff --git a/interactionMenu/lua/examples/onEntites.lua b/interactionMenu/lua/examples/onEntites.lua index e9a65d7..492ad5f 100644 --- a/interactionMenu/lua/examples/onEntites.lua +++ b/interactionMenu/lua/examples/onEntites.lua @@ -26,6 +26,7 @@ CreateThread(function() entity = entity, offset = vec3(0, 0, 0), maxDistance = 2.0, + icon = 'vending', indicator = { prompt = 'F', keyPress = { @@ -94,6 +95,7 @@ CreateThread(function() suppressGlobals = true, theme = 'red', entity = entity, + icon = 'vending', extra = { onSeen = function() print('seen') @@ -176,6 +178,7 @@ CreateThread(function() suppressGlobals = true, theme = 'cyan', entity = entity, + icon = 'vending', extra = { onExit = function() print('exit') @@ -228,6 +231,7 @@ CreateThread(function() suppressGlobals = true, theme = 'cyan', entity = entity, + icon = 'vending', indicator = { prompt = 'Press Enter', glow = true @@ -288,6 +292,7 @@ CreateThread(function() { position = positions[9], model = 'v_ret_fh_kitchtable', + icon = 'stove', options = { { video = { @@ -459,8 +464,8 @@ CreateThread(function() label = 'Delete Door', action = { type = 'sync', - func = function(data) - DeleteEntity(data.entity) + func = function(e) + DeleteEntity(e) end } } @@ -472,6 +477,7 @@ CreateThread(function() exports['interactionMenu']:Create { entity = entity, offset = vec3(0, 0, 0.3), + icon = 'glowingball', extra = { onExit = function() print('exit') @@ -520,6 +526,7 @@ CreateThread(function() exports['interactionMenu']:Create { entity = entity, offset = vec3(0, 0, 0), + icon = 'box', extra = { onExit = function() print('exit') @@ -680,6 +687,7 @@ CreateThread(function() entity = ent, offset = vec3(0, 0, 0), static = true, + icon = "stove2", options = { { label = 'Cook', diff --git a/interactionMenu/lua/examples/onModel.lua b/interactionMenu/lua/examples/onModel.lua index 5c03c5e..b7bd4e8 100644 --- a/interactionMenu/lua/examples/onModel.lua +++ b/interactionMenu/lua/examples/onModel.lua @@ -220,4 +220,46 @@ CreateThread(function() } } } + + + coords = vector4(-1999.05, 3178.58, 31.81, 147.54) + Util.spawnObject(`prop_paper_bag_01`, coords) + coords = vector4(-1999.05, 3179.58, 31.81, 147.54) + Util.spawnObject(`prop_paper_bag_01`, coords) + coords = vector4(-1998.05, 3178.58, 31.81, 147.54) + Util.spawnObject(`prop_paper_bag_01`, coords) + + exports['interactionMenu']:Create { + type = 'model', + model = `prop_paper_bag_01`, + offset = vec3(0, 0, 0), + maxDistance = 3.0, + options = { + { + label = 'Second On Model', + icon = 'fa fa-book', + action = { + func = function(e) + end + }, + } + } + } + + -- exports['interactionMenu']:Create { + -- type = 'model', + -- model = `prop_paper_bag_01`, + -- offset = vec3(0, 0, 0), + -- maxDistance = 3.0, + -- options = { + -- { + -- label = 'Pick', + -- icon = 'fa fa-book', + -- action = { + -- func = function(e) + -- end + -- }, + -- } + -- } + -- } end) diff --git a/interactionMenu/lua/examples/onZone.lua b/interactionMenu/lua/examples/onZone.lua index a304008..72e602f 100644 --- a/interactionMenu/lua/examples/onZone.lua +++ b/interactionMenu/lua/examples/onZone.lua @@ -1,4 +1,5 @@ if not DEVMODE then return end +if true then return end -- -- isPointInside CreateThread(function() diff --git a/interactionMenu/lua/examples/test.lua b/interactionMenu/lua/examples/test.lua new file mode 100644 index 0000000..d419798 --- /dev/null +++ b/interactionMenu/lua/examples/test.lua @@ -0,0 +1,46 @@ +-- CreateThread(function() +-- local veh_pos = vector4(-1974.9, 3178.76, 32.81, 59.65) +-- local vehicle = Util.spawnVehicle('adder', veh_pos) + +-- SetVehicleNumberPlateText(vehicle, 'swkeep') + +-- exports['interactionMenu']:createGlobal { +-- type = 'bones', +-- bone = 'platelight', +-- offset = vec3(0, 0, 0), +-- maxDistance = 2.0, +-- options = { +-- { +-- label = '[Debug] On All plates', +-- icon = 'fa fa-rectangle-ad', +-- action = { +-- type = 'sync', +-- func = function(entity) +-- print('Plate:', GetVehicleNumberPlateText(entity)) +-- end +-- } +-- } +-- } +-- } + +-- local pos = vector4(-1973.31, 3181.98, 32.81, 249.84) +-- local ped_ = Util.spawnPed(GetHashKey('cs_brad'), pos) + +-- exports['interactionMenu']:createGlobal { +-- type = 'peds', +-- offset = vec3(0, 0, 0), +-- maxDistance = 2.0, +-- options = { +-- { +-- label = '[Debug] On All peds', +-- icon = 'fa fa-rectangle-ad', +-- action = { +-- type = 'sync', +-- func = function(entity) +-- print('Plate:', GetVehicleNumberPlateText(entity)) +-- end +-- } +-- } +-- } +-- } +-- end)