local sin, cos, rad = math.sin, math.cos, math.rad local setMatrix = setElementMatrix local setPosition = setElementPosition local getPosition = getElementPosition local setDimension = setElementDimension local getDimension = getElementDimension local setInterior = setElementInterior local getInterior = getElementInterior local getBoneMatrix = getElementBoneMatrix local setCollisions = setElementCollisionsEnabled local isLineClear = isLineOfSightClear; local isOnScreen = isElementOnScreen local isElement = isElement local isStreamedIn = isElementStreamedIn local getType = getElementType local getAlpha = getElementAlpha local setAlpha = setElementAlpha local getCameraMatrix = getCameraMatrix pAttach = { instances = {}, pedInstances = {}, inStreamPeds = {}, preparedToTimerInstances = {}, preparedToRenderInstances = {}, preparedRenderMap = {}, distanceUpdateConfig = { nearDistance = 20, -- até 20m atualiza todo frame midDistance = 50, -- até 50m atualiza a cada 2 frames farDistance = 90, -- até 90m atualiza a cada 4 frames nearInterval = 0, -- 0 = todo frame midInterval = 33, -- ~30 FPS farInterval = 66, -- ~15 FPS veryFarInterval = 120, -- ~8 FPS stoppedMultiplier = 1.8, -- ped parado atualiza mais devagar }, lastTimerUpdate = 0, pedsProcessedAdded = false, attach = function(self, element, ped, _boneid, ox, oy, oz, rx, ry, rz, updateLightning) local boneid = boneIDNames[_boneid] or tonumber(_boneid) or false assert(isElement(element) and getType(element) ~= 'player', 'Expected element (except: player) at argument 1, got '..type(element)) assert(isElement(ped), 'Expected element at argument 2, got '..type(ped)) assert(boneid and boneIDs[boneid], 'Expected valid bone-id or bone-name at argument 3, got '..tostring(_boneid)..'') if self:isAttached(element) then return false end setPosition(element, 0, 0, 10000) setDimension(element, getDimension(ped)) setInterior(element, getInterior(ped)) setCollisions(element, false) local pedIns = self.pedInstances[ped] local pedType = getType(ped) if not pedIns then pedIns = { count = 1, pedType = pedType, list = {} } self.pedInstances[ped] = pedIns if ped ~= localPlayer then addEventHandler('onClientElementStreamIn', ped, self.onStreamIn) addEventHandler('onClientElementStreamOut', ped, self.onStreamOut) if pedType == 'ped' then addEventHandler('onClientElementDestroy', ped, self.onPedDestroy) end end addEventHandler('onClientElementDimensionChange', ped, self.onDimensionChange) addEventHandler('onClientElementInteriorChange', ped, self.onInteriorChange) else pedIns.count = pedIns.count + 1 end pedIns.list[element] = { element = element, ped = ped, boneid = boneid, _boneid = _boneid, ox = ox or 0, oy = oy or 0, oz = oz or 0, rx = rx or 0, ry = ry or 0, rz = rz or 0, rotMat = self:calculateRotMat(rx or 0, ry or 0, rz or 0), updateLightning = updateLightning or false, customLODDistance = getElementData(element, 'Object:CustomLODDistance') or false, disableRender = getElementData(element, 'Object:DisableRender') or false, vehicleDisableRender = getElementData(element, 'Object:VehicleDisableRender') or false, matrix = { {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 1} }, prevOutOfScreen = true, lastUpdated = 0, lastMatrixUpdate = 0, lastPedX = false, lastPedY = false, lastPedZ = false, lastPedMoveTick = 0, pedStopped = false, updateInterval = 0, } self.instances[element] = ped if ped == localPlayer or isStreamedIn(ped) then if self.inStreamPeds[ped] then self:refreshRender() else self:addToStream(ped) end end addEventHandler('onClientElementDestroy', element, self.onElementDestroy) addEventHandler('onClientElementDataChange', element, self.onElementDataChange) return true end, detach = function(self, element) if not self:isAttached(element) then return false end local ped = self.instances[element] local pedIns = self.pedInstances[ped] if not pedIns then self.instances[element] = nil setPosition(element, 0, 0, 10000) setCollisions(element, true) return true end pedIns.count = pedIns.count - 1 pedIns.list[element] = nil self:_removeFromRender(element) removeEventHandler('onClientElementDestroy', element, self.onElementDestroy) removeEventHandler('onClientElementDataChange', element, self.onElementDataChange) self.instances[element] = nil setPosition(element, 0, 0, 10000) setCollisions(element, true) if pedIns.count <= 0 then if isElement(ped) then removeEventHandler('onClientElementStreamIn', ped, self.onStreamIn) removeEventHandler('onClientElementStreamOut', ped, self.onStreamOut) removeEventHandler('onClientElementDimensionChange', ped, self.onDimensionChange) removeEventHandler('onClientElementInteriorChange', ped, self.onInteriorChange) if pedIns.pedType == 'ped' then removeEventHandler('onClientElementDestroy', ped, self.onPedDestroy) end end self:removeFromStream(ped) self.pedInstances[ped] = nil else self:refreshRender() end return true end, detachAll = function(self, ped) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) if self.pedInstances[ped] then for element in next, self.pedInstances[ped].list, nil do self:detach(element) end end return true end, hasAnyStreamAttached = function(self) for ped in next, self.inStreamPeds, nil do local pedIns = self.pedInstances[ped] if pedIns and pedIns.count and pedIns.count > 0 then return true end end return false end, isAttached = function(self, element) return (element and self.instances[element]) and true or false end, setDetails = function(self, element, ped, _boneid, ox, oy, oz, rx, ry, rz) assert(isElement(element), 'Expected element at argument 1, got '..type(element)) if not self:isAttached(element) then return false end local details = self:getDetails(element) if ped then self:setPed(element, ped) end if _boneid then self:setBone(element, _boneid) end self:setPositionOffset(element, ox or details[4], oy or details[5], oz or details[6]) self:setRotationOffset(element, rx or details[7], ry or details[8], rz or details[9]) return true end, getDetails = function(self, element) assert(isElement(element), 'Expected element at argument 1, got '..type(element)) if not self:isAttached(element) then return false end local v = self.pedInstances[self.instances[element]].list[element] return v and { v.element, v.ped, v._boneid, v.ox, v.oy, v.oz, v.rx, v.ry, v.rz } or false end, getAttacheds = function(self, ped) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) local list = {} if self.pedInstances[ped] then for element in next, self.pedInstances[ped].list, nil do list[ #list + 1 ] = element end end return list end, setPositionOffset = function(self, element, x, y, z) assert(isElement(element), 'Expected element at argument 1, got '..type(element)) if not self:isAttached(element) then return false end local ped = self.instances[element] local ins = self.pedInstances[ped].list[element] ins.ox = x or 0 ins.oy = y or 0 ins.oz = z or 0 return true end, setRotationOffset = function(self, element, x, y, z) assert(isElement(element), 'Expected element at argument 1, got '..type(element)) if not self:isAttached(element) then return false end local ped = self.instances[element] local ins = self.pedInstances[ped].list[element] ins.rx = x or 0 ins.ry = y or 0 ins.rz = z or 0 ins.rotMat = self:calculateRotMat(x or 0, y or 0, z or 0) return true end, setBone = function(self, element, _boneid) local boneid = boneIDNames[_boneid] or tonumber(_boneid) or false assert(isElement(element), 'Expected element at argument 1, got '..type(element)) assert(boneid and boneIDs[boneid], 'Expected valid bone-id or bone-name at argument 2, got '..tostring(_boneid)..'') if not self:isAttached(element) then return false end local ped = self.instances[element] local ins = self.pedInstances[ped].list[element] ins._boneid = _boneid ins.boneid = boneid return true end, setPed = function(self, element, ped) assert(isElement(element), 'Expected element at argument 1, got '..type(element)) assert(isElement(ped), 'Expected element at argument 2, got '..type(ped)) if not self:isAttached(element) then return false end local details = self:getDetails(element) self:detach(element) self:attach(element, ped, details[3], details[4], details[5], details[6], details[7], details[8], details[9]) return true end, setVisible = function(self, element, bool) assert(isElement(element), 'Expected element at argument 1, got '..type(ped)) if not self:isAttached(element) then return false end return setAlpha(element, bool and 255 or 0) end, setVisibleAll = function(self, ped, bool) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) if self.pedInstances[ped] then for element in next, self.pedInstances[ped].list, nil do self:setVisible(element, bool) end end return true end, -- deprecated invisibleAll = function(self, ped, bool) self:setVisibleAll(ped, not bool) end, addToStream = function(self, ped) if not self.inStreamPeds[ped] then self.inStreamPeds[ped] = true if self.pedInstances[ped] then self:refreshRender() end return true end return false end, removeFromStream = function(self, ped) if self.inStreamPeds[ped] then if self.pedInstances[ped] then for element in next, self.pedInstances[ped].list, nil do self:_removeFromRender(element) end end self.inStreamPeds[ped] = nil self:refreshRender() return true end return false end, onStreamIn = function() pAttach:addToStream(source) end, onStreamOut = function() pAttach:removeFromStream(source) end, onElementDataChange = function(dataName, oldValue, newValue) local self = pAttach if not self.instances[source] then return end local ped = self.instances[source] local pedIns = self.pedInstances[ped] if not pedIns or not pedIns.list[source] then return end local data = pedIns.list[source] if dataName == 'Object:CustomLODDistance' then data.customLODDistance = newValue or false elseif dataName == 'Object:DisableRender' then data.disableRender = newValue or false if data.disableRender then self:_removeFromRender(source) end elseif dataName == 'Object:VehicleDisableRender' then data.vehicleDisableRender = newValue or false end end, onDimensionChange = function(old, new) if pAttach.pedInstances[source] then for element in next, pAttach.pedInstances[source].list, nil do setDimension(element, new) end end end, onInteriorChange = function(old, new) if pAttach.pedInstances[source] then for element in next, pAttach.pedInstances[source].list, nil do setInterior(element, new) end end end, onElementDestroy = function() pAttach:detach(source) end, onPedDestroy = function() pAttach:detachAll(source) end, refreshRender = function(self) local tbl = {} local len = 0 for ped in next, self.inStreamPeds, nil do len = len + table.size(self.pedInstances[ped].list) if len > 0 then break end end self.preparedToRenderInstances = {} self.preparedRenderMap = {} self.onPedsTimer() if len > 0 and not self.pedsProcessedAdded then self.pedsProcessedAdded = true addEventHandler('onClientPedsProcessed', root, self.onPedsProcessed, true, 'low-99999') elseif len == 0 and self.pedsProcessedAdded then self.pedsProcessedAdded = false removeEventHandler('onClientPedsProcessed', root, self.onPedsProcessed) end return true end, getUpdateIntervalByDistance = function(self, distance, pedStopped, inVehicle) local cfg = self.distanceUpdateConfig local interval = cfg.nearInterval if distance <= cfg.nearDistance then interval = cfg.nearInterval elseif distance <= cfg.midDistance then interval = cfg.midInterval elseif distance <= cfg.farDistance then interval = cfg.farInterval else interval = cfg.veryFarInterval end -- Dentro de veículo precisa atualizar melhor, porque animação/posição muda mais rápido visualmente if inVehicle then interval = math.min(interval, cfg.midInterval) end if pedStopped and interval > 0 then interval = math.floor(interval * cfg.stoppedMultiplier) end return interval end, isPedStoppedCached = function(self, data, ped, now) local px, py, pz = getElementPosition(ped) if not data.lastPedX then data.lastPedX = px data.lastPedY = py data.lastPedZ = pz data.lastPedMoveTick = now data.pedStopped = false return false, px, py, pz end local dx = px - data.lastPedX local dy = py - data.lastPedY local dz = pz - data.lastPedZ local movedSq = dx * dx + dy * dy + dz * dz -- 0.0025 = aproximadamente 5cm if movedSq > 0.0025 then data.lastPedX = px data.lastPedY = py data.lastPedZ = pz data.lastPedMoveTick = now data.pedStopped = false else -- Se ficou mais de 300ms praticamente sem mover, considera parado data.pedStopped = now - data.lastPedMoveTick > 300 end return data.pedStopped, px, py, pz end, calculateRotMat = function(self, rx, ry, rz) local rx, ry, rz = rad(rx), rad(ry), rad(rz) local syaw, cyaw = sin(rx), cos(rx) local spitch, cpitch = sin(ry), cos(ry) local sroll, croll = sin(rz), cos(rz) return { { cpitch*croll - spitch*syaw*sroll, cpitch*sroll + spitch*syaw*croll, -spitch*cyaw }, { -cyaw*sroll, cyaw*croll, syaw }, { spitch*croll + cpitch*syaw*sroll, spitch*sroll - cpitch*syaw*croll, cpitch*cyaw } } end, _addToRender = function(self, element, data) if self.preparedRenderMap[element] then return false end self.preparedRenderMap[element] = data table.insert(self.preparedToRenderInstances, data) return true end, _removeFromRender = function(self, element) local removed = false for i = #self.preparedToRenderInstances, 1, -1 do local data = self.preparedToRenderInstances[i] if data then if data.element == element then setPosition(data.element, 0, 0, 10000) data.prevOutOfScreen = true self.preparedRenderMap[data.element] = nil table.remove(self.preparedToRenderInstances, i) return true elseif data.ped == element then setPosition(data.element, 0, 0, 10000) data.prevOutOfScreen = true self.preparedRenderMap[data.element] = nil table.remove(self.preparedToRenderInstances, i) removed = true end end end return removed end, _getFromRender = function(self, element) return self.preparedRenderMap[element] or false end, onPedsTimer = function() local self = pAttach local cx, cy, cz = getCameraMatrix() local lodDistance = (getPedsLODDistance() or 200) + 5 local vehlodDistance = (getVehiclesLODDistance() or 200) local LodsDistances = {} local PedVehicles = {} if next(self.pedInstances) == nil then self.preparedToRenderInstances = {} self.preparedRenderMap = {} if self.pedsProcessedAdded then self.pedsProcessedAdded = false removeEventHandler('onClientPedsProcessed', root, self.onPedsProcessed) end return end for ped, v in next, (self.inStreamPeds or {}), nil do local v = self.pedInstances[ped] if v then local px, py, pz = getPosition(ped) local IsVisible = ped == localPlayer or isOnScreen(ped) if IsVisible then local Vehicle = PedVehicles[ped] or getPedOccupiedVehicle(ped) or false local distance = getDistance3D(cx, cy, cz, px, py, pz) local baseLodDistance = Vehicle and vehlodDistance or lodDistance -- Prevenir de que abusem de baixas renderizacoes para ver attachs (weapons, etc) em veiculos if distance < baseLodDistance then for element, data in next, (v.list or {}), nil do if not data.model then data.model = getElementModel(element); end if not LodsDistances[data.model] then LodsDistances[data.model] = ((engineGetModelLODDistance(data.model) or 0)) + 15 end if not data.ModelLodDistance or data.ModelLodDistance ~= LodsDistances[data.model] then data.ModelLodDistance = LodsDistances[data.model] end local elementLodDistance = ((Vehicle and data.vehicleDisableRender) or data.disableRender) and 0 or math.min(baseLodDistance + 15, data.customLODDistance and (data.customLODDistance + 15) or data.ModelLodDistance) if (distance < elementLodDistance) then data.distanceToCamera = distance data.inVehicle = Vehicle and true or false if self:_addToRender(element, data) and not self.pedsProcessedAdded then self.pedsProcessedAdded = true addEventHandler('onClientPedsProcessed', root, self.onPedsProcessed, true, 'low-99999') end else self:_removeFromRender(element) end end else self:_removeFromRender(ped) end else self:_removeFromRender(ped) end end end if self.pedsProcessedAdded and not self:hasAnyStreamAttached() then self.pedsProcessedAdded = false removeEventHandler('onClientPedsProcessed', root, self.onPedsProcessed) end return true end, onPedsProcessed = function() local self = pAttach local boneMatCache = {} local pedsAlpha = {} local Now = getTickCount() local TimerInterval = 100 if not self.lastTimerUpdate or Now >= self.lastTimerUpdate then self.onPedsTimer() self.lastTimerUpdate = Now + TimerInterval end for i = #self.preparedToRenderInstances, 1, -1 do local data = self.preparedToRenderInstances[i] local element, ped = data.element, data.ped local boneid = data.boneid local ox, oy, oz = data.ox, data.oy, data.oz local pedAlpha = pedsAlpha[ped]; if pedAlpha == nil then pedAlpha = getAlpha(ped) or 0 pedsAlpha[ped] = pedAlpha end if (isOnScreen(ped) and pedAlpha > 0) then local distance = data.distanceToCamera or 0 local inVehicle = data.inVehicle or false local updateInterval = self:getUpdateIntervalByDistance(distance, self:isPedStoppedCached(data, ped, Now), inVehicle) if updateInterval <= 0 or not data.lastMatrixUpdate or Now - data.lastMatrixUpdate >= updateInterval then local bMCache = boneMatCache[ped] local boneMat = false if not bMCache then bMCache = {} boneMatCache[ped] = bMCache end if not bMCache[boneid] then boneMat = getBoneMatrix(ped, boneid) bMCache[boneid] = boneMat else boneMat = bMCache[boneid] end if boneMat then local bM1X, bM1Y, bM1Z = boneMat[1][1], boneMat[1][2], boneMat[1][3] local bM2X, bM2Y, bM2Z = boneMat[2][1], boneMat[2][2], boneMat[2][3] local bM3X, bM3Y, bM3Z = boneMat[3][1], boneMat[3][2], boneMat[3][3] local bM4X, bM4Y, bM4Z = boneMat[4][1], boneMat[4][2], boneMat[4][3] local rotMat = data.rotMat local rM1X, rM1Y, rM1Z = rotMat[1][1], rotMat[1][2], rotMat[1][3] local rM2X, rM2Y, rM2Z = rotMat[2][1], rotMat[2][2], rotMat[2][3] local rM3X, rM3Y, rM3Z = rotMat[3][1], rotMat[3][2], rotMat[3][3] local matrix = data.matrix matrix[1][1] = rM1X * bM1X + rM1Y * bM2X + rM1Z * bM3X matrix[1][2] = rM1X * bM1Y + rM1Y * bM2Y + rM1Z * bM3Y matrix[1][3] = rM1X * bM1Z + rM1Y * bM2Z + rM1Z * bM3Z matrix[1][4] = 0 matrix[2][1] = rM2X * bM1X + rM2Y * bM2X + rM2Z * bM3X matrix[2][2] = rM2X * bM1Y + rM2Y * bM2Y + rM2Z * bM3Y matrix[2][3] = rM2X * bM1Z + rM2Y * bM2Z + rM2Z * bM3Z matrix[2][4] = 0 matrix[3][1] = rM3X * bM1X + rM3Y * bM2X + rM3Z * bM3X matrix[3][2] = rM3X * bM1Y + rM3Y * bM2Y + rM3Z * bM3Y matrix[3][3] = rM3X * bM1Z + rM3Y * bM2Z + rM3Z * bM3Z matrix[3][4] = 0 matrix[4][1] = bM4X + ox * bM1X + oy * bM2X + oz * bM3X matrix[4][2] = bM4Y + ox * bM1Y + oy * bM2Y + oz * bM3Y matrix[4][3] = bM4Z + ox * bM1Z + oy * bM2Z + oz * bM3Z matrix[4][4] = 1 setMatrix(element, matrix) if data.updateLightning then local pedlightning = getElementLighting(ped) local lightning = getElementLighting(element) if pedlightning and lightning and pedlightning ~= lightning then setElementLighting(element, pedlightning) end end data.prevOutOfScreen = false data.lastUpdated = Now end end else if not data.prevOutOfScreen then setPosition(element, 0, 0, 10000) data.prevOutOfScreen = true data.lastUpdated = Now end end end end, } boneIDs = { [1] = true, [2] = true, [3] = true, [4] = true, [5] = true, [6] = true, [7] = true, [8] = true, [21] = true, [22] = true, [23] = true, [24] = true, [25] = true, [26] = true, [31] = true, [32] = true, [33] = true, [34] = true, [35] = true, [36] = true, [41] = true, [42] = true, [43] = true, [44] = true, [51] = true, [52] = true, [53] = true, [54] = true, } boneIDNames = { ['pelvis'] = 1, ['pelvis2'] = 2, ['spine'] = 3, ['neck'] = 4, ['head'] = 5, ['head2'] = 6, ['head3'] = 7, ['jaw'] = 8, ['right-upper-torso'] = 21, ['right-shoulder'] = 22, ['right-elbow'] = 23, ['right-wrist'] = 24, ['right-hand'] = 25, ['right-thumb'] = 26, ['left-upper-torso'] = 31, ['left-shoulder'] = 32, ['left-elbow'] = 33, ['left-wrist'] = 34, ['left-hand'] = 35, ['left-thumb'] = 36, ['left-hip'] = 41, ['left-knee'] = 42, ['left-tankle'] = 43, ['left-foot'] = 44, ['right-hip'] = 51, ['right-knee'] = 52, ['right-tankle'] = 53, ['right-foot'] = 54, -- extra ['backpack'] = 3, ['weapon'] = 24, } addEvent('pAttach:receiveCache', true) addEventHandler('pAttach:receiveCache', resourceRoot, function(cache) for _, data in next, cache, nil do pAttach:attach(unpack(data)) end end)