local sin, cos, rad = math.sin, math.cos, math.rad local getPosition = getElementPosition local getDimension = getElementDimension local getInterior = getElementInterior local getBoneMatrix = getElementBoneMatrix local isOnScreen = isElementOnScreen local isElement = isElement local isStreamedIn = isElementStreamedIn local getType = getElementType local getAlpha = getElementAlpha local getCameraMatrix = getCameraMatrix ASYNC_LOADING_STREAMING = true local mAttach = {} mAttach.instances = {} mAttach.RequestedStream = {} mAttach.pedInstances = {} mAttach.inStreamPeds = {} mAttach.preparedToTimerInstances = {} mAttach.preparedToRenderInstances = {} mAttach.pedsProcessedAdded = false function mAttach:attach(ped, modelID, _boneid, ox, oy, oz, rx, ry, rz) local boneid = boneIDNames[_boneid] or tonumber(_boneid) or false assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) assert(boneid and boneIDs[boneid], 'Expected valid bone-id or bone-name at argument 3, got '..tostring(_boneid)..'') if self:isAttached(ped, modelID) then return false end 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) addEventHandler(pedType == 'ped' and 'onClientElementDestroy' or 'onClientPlayerQuit', ped, self.onPedDestroy) end else pedIns.count = pedIns.count + 1 end pedIns.list[modelID] = { modelID = modelID, 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, } if ped == localPlayer or isStreamedIn(ped) then if self.inStreamPeds[ped] then self:refreshRender() else self:addToStream(ped) end end return true end function mAttach:detach(ped, modelID) if not self:isAttached(ped, modelID) then return false end local pedIns = self.pedInstances[ped] pedIns.count = pedIns.count - 1 if pedIns.count == 0 then if isElement(ped) then removeEventHandler('onClientElementStreamIn', ped, self.onStreamIn) removeEventHandler('onClientElementStreamOut', ped, self.onStreamOut) removeEventHandler(pedIns.pedType == 'ped' and 'onClientElementDestroy' or 'onClientPlayerQuit', ped, self.onPedDestroy) end self.pedInstances[ped] = nil self:removeFromStream(ped) else pedIns.list[modelID] = nil self:refreshRender() end return true end function mAttach:detachAll(ped) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) if self.pedInstances[ped] then for modelID in next, self.pedInstances[ped].list, nil do self:detach(ped, modelID) end end return true end function mAttach:isAttached(ped, modelID) return (ped and self.pedInstances[ped] and self.pedInstances[ped].list[modelID]) and true or false end function mAttach:setDetails(ped, modelID, _boneid, ox, oy, oz, rx, ry, rz) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) if not self:isAttached(ped, modelID) then return false end local details = self:getDetails(ped, modelID) if ped then self:setPed(ped, modelID) end if _boneid then self:setBone(ped, modelID, _boneid) end self:setPositionOffset(ped, modelID, ox or details[4], oy or details[5], oz or details[6]) self:setRotationOffset(ped, modelID, rx or details[7], ry or details[8], rz or details[9]) return true end function mAttach:getDetails(ped, modelID) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) if not self:isAttached(ped, modelID) then return false end local v = self.pedInstances[ped].list[modelID] return v and { v.modelID, v.ped, v._boneid, v.ox, v.oy, v.oz, v.rx, v.ry, v.rz } or false end function mAttach:getAttacheds(ped) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) local list = {} if self.pedInstances[ped] then for modelId in next, self.pedInstances[ped].list, nil do list[ #list + 1 ] = modelId end end return list end function mAttach:setPositionOffset(ped, modelID, x, y, z) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) if not self:isAttached(ped, modelID) then return false end local ins = self.pedInstances[ped].list[modelID] ins.ox = x or ins.ox or 0 ins.oy = y or ins.oy or 0 ins.oz = z or ins.oz or 0 return true end function mAttach:setRotationOffset(ped, modelID, x, y, z) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) if not self:isAttached(ped, modelID) then return false end local ins = self.pedInstances[ped].list[modelID] ins.rx = x or ins.rx or 0 ins.ry = y or ins.ry or 0 ins.rz = z or ins.rz or 0 return true end function mAttach:setBone(ped, modelID, _boneid) local boneid = boneIDNames[_boneid] or tonumber(_boneid) or false assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) assert(boneid and boneIDs[boneid], 'Expected valid bone-id or bone-name at argument 3, got '..tostring(_boneid)..'') if not self:isAttached(ped, modelID) then return false end local ins = self.pedInstances[ped].list[modelID] ins._boneid = _boneid or ins._boneid ins.boneid = boneid or ins.boneid return true end function mAttach:setVisible(ped, modelID, bool) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) assert(type(modelID) == 'number', 'Expected modelID at argument 2, got '..type(modelID)) if not self:isAttached(ped, modelID) then return false end local ins = self.pedInstances[ped].list[modelID] ins.visible = bool or ins.visible or true return true end function mAttach:setVisibleAll(ped, bool) assert(isElement(ped), 'Expected element at argument 1, got '..type(ped)) if self.pedInstances[ped] then for modelID in next, self.pedInstances[ped].list, nil do self:setVisible(ped, modelID, bool) end end return true end function mAttach:invisibleAll(ped, bool) self:setVisibleAll(ped, not bool) end function mAttach:addToStream(ped) if not self.inStreamPeds[ped] then self.inStreamPeds[ped] = true mAttach:refreshRender() return true end return false end function mAttach:removeFromStream(ped) if self.inStreamPeds[ped] then if self.pedInstances[ped] and next(self.pedInstances[ped].list) then for modelID, data in next, self.pedInstances[ped].list, nil do if self.RequestedStream[modelID] then engineStreamingReleaseModel(modelID, true) self.RequestedStream[modelID] = nil end end end self.inStreamPeds[ped] = nil self:refreshRender() return true end return false end function mAttach:onStreamIn() mAttach:addToStream(source) end function mAttach:onStreamOut() mAttach:removeFromStream(source) end function mAttach:onPedDestroy() mAttach:detachAll(source) end function mAttach:refreshRender() local tbl = {} local modelsInStream = {} local len = 0 for ped in next, self.inStreamPeds, nil do for modelID, data in next, self.pedInstances[ped].list, nil do len = len + 1 tbl[len] = data if not self.RequestedStream[modelID] then self.RequestedStream[modelID] = engineStreamingRequestModel(modelID, true, ASYNC_LOADING_STREAMING) end if not modelsInStream[modelID] then modelsInStream[modelID] = true end end end for modelID, data in next, self.RequestedStream, nil do if not modelsInStream[modelID] then engineStreamingReleaseModel(modelID, true) self.RequestedStream[modelID] = nil end end self.preparedToRenderInstances = tbl if len > 0 and not self.pedsProcessedAdded then addEventHandler('onClientPedsProcessed', root, self.onPedsProcessed, true, 'low-999') self.pedsProcessedAdded = true elseif len == 0 and self.pedsProcessedAdded then removeEventHandler('onClientPedsProcessed', root, self.onPedsProcessed, true, 'low-999') self.pedsProcessedAdded = false end return true end function mAttach:onPedsProcessed() if not mAttach.preparedToRenderInstances or next(mAttach.preparedToRenderInstances) == nil then return end local preparedToRenderInstances = mAttach.preparedToRenderInstances local boneMatCache = {} local pedsPositionsCache = {} local pedsAlphaCache = {} local pedsInteriorCache = {} local pedsDimensionCache = {} local pedsOnScreenCache = {} local cx, cy, cz = getCameraMatrix() local lodDistance = (getPedsLODDistance() or 200) + 5 local mInterior = getInterior(localPlayer) local mDimension = getDimension(localPlayer) for i = 1, #preparedToRenderInstances do local data = preparedToRenderInstances[i] or {}; local ped = data.ped local boneid = data.boneid local ox, oy, oz = data.ox, data.oy, data.oz local rx, ry, rz = data.rx, data.ry, data.rz local model = data.modelID if not pedsPositionsCache[ped] then pedsPositionsCache[ped] = {getPosition(ped)} end if not pedsAlphaCache[ped] then pedsAlphaCache[ped] = getAlpha(ped) end if not pedsInteriorCache[ped] then pedsInteriorCache[ped] = getInterior(ped) end if not pedsDimensionCache[ped] then pedsDimensionCache[ped] = getDimension(ped) end if not pedsOnScreenCache[ped] then pedsOnScreenCache[ped] = isOnScreen(ped) end local pedPosition = pedsPositionsCache[ped] or {0, 0, 0} local distance = getDistance2D(cx, cy, pedPosition[1], pedPosition[2]) or 1 local pedInterior = pedsInteriorCache[ped] or 0 local pedDimension = pedsDimensionCache[ped] or 0 local pedAlpha = pedsAlphaCache[ped] or 0 local pedOnScreen = pedsOnScreenCache[ped] or false if pedOnScreen and distance < lodDistance and pedAlpha > 1 and pedInterior == mInterior and pedDimension == mDimension 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 x, y, z = boneMat[4][1] + ox, boneMat[4][2] + oy, boneMat[4][3] + oz dxDrawModel3D(model, x, y, z, rx, ry, rz) data.prevOutOfScreen = false end else if not data.prevOutOfScreen then data.prevOutOfScreen = true end end end end addEvent(resource.name..':receiveCache', true) addEventHandler(resource.name..':receiveCache', resourceRoot, function(cache) if not cache then return end if next(cache) == nil then return end for _, data in next, cache, nil do mAttach:attach(unpack(data)) end end) boneIDs = { [0] = true, [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 = { ['root'] = 0, ['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, } _G.mAttach = mAttach