diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/.fxap b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/.fxap new file mode 100644 index 000000000..f01780bd6 Binary files /dev/null and b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/.fxap differ diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/checkInv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/checkInv.lua new file mode 100644 index 000000000..c7a602bab --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/checkInv.lua @@ -0,0 +1,28 @@ +config.useDefaultInventory = true + +local function log(msg) + print(string.format('\x1b[32m[tgiann-weapons-on-back]\x1b[0m %s', msg)) +end + +-- Chezza Studios inventory works fine with normal esx(don't add to the list) +local inventorys = { + "tgiann-inventory", + "mf-inventory", + "ox_inventory", + "core_inventory", + "qs-inventory", + "codem-inventory", + "origen_inventory" +} + +for i = 1, #inventorys do + local inventory = inventorys[i] + local isStarted = GetResourceState(inventory) == "started" + if isStarted then + config[inventory] = true + config.useDefaultInventory = false + log(string.format("Started with %s inventory", inventory)) + end +end + +if config.useDefaultInventory then log(string.format("Started with %s default inventory", config.framework == "qb" and "QB" or "ESX")) end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/codem_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/codem_inv.lua new file mode 100644 index 000000000..490b46e57 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/codem_inv.lua @@ -0,0 +1,89 @@ +if not config["codem-inventory"] then return end + +local playerJob = "" +local lastItems = {} + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + SetTimeout(2000, function() -- some waiting time because the character's inventory data is loaded later + lastItems = exports['codem-inventory']:GetClientPlayerInventory() + weaponCheck() + end) +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +RegisterNetEvent('codem-inventory:client:additem') +AddEventHandler('codem-inventory:client:additem', function(slot, data) + lastItems[tostring(slot)] = data + weaponCheck() +end) + +RegisterNetEvent('codem-inventory:client:removeitemtoclientInventory') +AddEventHandler('codem-inventory:client:removeitemtoclientInventory', function(slot, amount) + slot = tostring(slot) + if lastItems[slot] then + local itemAmount = lastItems[slot].count or lastItems[slot].amount + if itemAmount == amount then + lastItems[slot] = nil + end + end + weaponCheck() +end) + +RegisterNetEvent('codem-inventory:client:clearinventory') +AddEventHandler('codem-inventory:client:clearinventory', function() + lastItems = {} + weaponCheck() +end) + +RegisterNetEvent('codem-inventory:client:setitembyslot') +AddEventHandler('codem-inventory:client:setitembyslot', function(slot, itemData) + lastItems[tostring(slot)] = itemData + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if key == item.info?.serie or item.name then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and item.type == "weapon" then + self.Functions.AddWeapon({ + weapon = item.name, + key = item?.info?.serie or item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.info.tgiattachments, joaat(item.name)) or item.info.attachments, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/core_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/core_inv.lua new file mode 100644 index 000000000..4c3a8ae84 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/core_inv.lua @@ -0,0 +1,116 @@ +-- https://docs.c8re.store/core-inventory/api +if not config.core_inventory then return end + +local function splitStr(str, delimiter) + local result = {} + local from = 1 + local delim_from, delim_to = string.find(str, delimiter, from) + while delim_from do + result[#result + 1] = string.sub(str, from, delim_from - 1) + from = delim_to + 1 + delim_from, delim_to = string.find(str, delimiter, from) + end + result[#result + 1] = string.sub(str, from) + return result +end + +local playerJob = "" +local invItems = {} +local playerItems = {} + +local function getInventoryItems() + tgiCore.cbFunction('tgiann-weapons-on-back:core_inventory:server:getInventory', function(newPlayerItems) + playerItems = newPlayerItems + weaponCheck(true) + end) +end + +RegisterNetEvent('core_inventory:client:sync', function(inventory, data) + local split = splitStr(inventory, '-')[1] + if config.enableInv[split] then + playerItems[split] = data.content + end +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + if not PlayerData then return end + playerJob = PlayerData.job.name + getInventoryItems(PlayerData.identifier) +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck(false) +end) + +if config.framework == "qb" then + RegisterNetEvent('QBCore:Player:SetPlayerData', function(PlayerData) + playerJob = PlayerData.job.name + if not config.enableInv["content"] then return end + playerItems["content"] = PlayerData?.items + weaponCheck(true) + end) +else + RegisterNetEvent('esx:addInventoryItem') + AddEventHandler('esx:addInventoryItem', function(job) + local PlayerData = exports["tgiann-core"]:getPlayerData() + if not PlayerData then return end + getInventoryItems(PlayerData.identifier) + end) + + RegisterNetEvent('esx:removeInventoryItem') + AddEventHandler('esx:removeInventoryItem', function(job) + local PlayerData = exports["tgiann-core"]:getPlayerData() + if not PlayerData then return end + getInventoryItems(PlayerData.identifier) + end) +end + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(invItems) do + if item and key == (item.metadata?.serial or item.name) then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck(updateData) + if updateData then + invItems = {} + for _, inv in pairs(playerItems) do + for _, item in pairs(inv) do + invItems[#invItems + 1] = item + end + end + end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(invItems) do + if item and string.find(string.lower(item.name), "weapon") then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.metadata?.serial or item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.metadata.tgiattachments, joaat(item.name)) or item.metadata.attachments, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/esx_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/esx_inv.lua new file mode 100644 index 000000000..ff1881be5 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/esx_inv.lua @@ -0,0 +1,88 @@ +if config.framework ~= "esx" then return end +if not config.useDefaultInventory then return end + +local playerJob = "" +local lastItems = {} + +local function getInventoryItems() + tgiCore.cbFunction('tgiann-weapons-on-back:esx_inv:server:getInventory', function(playerItems, loadout) + local itemList = {} + -- ESX is suck + if loadout and #loadout > 0 then + for i = 1, #loadout do + itemList[#itemList + 1] = loadout[i] + end + end + + if playerItems and #playerItems > 0 then + for i = 1, #playerItems do + if playerItems[i].count > 0 then + itemList[#itemList + 1] = playerItems[i] + end + end + end + lastItems = itemList + weaponCheck() + end) +end + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + getInventoryItems() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('esx:addInventoryItem') +AddEventHandler('esx:addInventoryItem', function(job) + getInventoryItems() +end) + +RegisterNetEvent('esx:removeInventoryItem') +AddEventHandler('esx:removeInventoryItem', function(job) + getInventoryItems() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if item and key == item.name then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + self.Functions.CheckWeaponIsRemoved() + Wait(100) + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and string.find(string.lower(item.name), "weapon") then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item?.info?.tgiattachments, joaat(item.name)) or item.components, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/main.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/main.lua new file mode 100644 index 000000000..786946d5f --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/main.lua @@ -0,0 +1,99 @@ +tgiCore = nil +CreateThread(function() + while not tgiCore do + tgiCore = tgiCoreExports:getCore() + Wait(200) + end +end) + +local function LoadModel(model) + if HasModelLoaded(model) then return end + RequestModel(model) + while not HasModelLoaded(model) do Wait(0) end +end + +self.Functions.AddAttachments = function(entity, weaponName, weaponHash, attachments) + if not attachments then return end + if config.tgiann_attachments then + for _, data in pairs(attachments) do + local model = GetWeaponComponentTypeModel(data.component) + if model ~= 0 then + LoadModel(model) + GiveWeaponComponentToWeaponObject(entity, data.component) + SetModelAsNoLongerNeeded(data.component) + else + SetWeaponObjectTintIndex(entity, data.component) + end + end + elseif config.core_inventory then + for _, data in pairs(attachments) do + local model = GetWeaponComponentTypeModel(data.componentHash) + if model ~= 0 then + LoadModel(model) + GiveWeaponComponentToWeaponObject(entity, data.componentHash) + SetModelAsNoLongerNeeded(data.componentHash) + else + SetWeaponObjectTintIndex(entity, data.componentHash) + end + end + elseif config.ox_inventory then + if not oxItems then oxItems = exports.ox_inventory:Items() end + for i = 1, #attachments do + local components = oxItems[attachments[i]].client.component + for v = 1, #components do + local component = components[v] + if DoesWeaponTakeWeaponComponent(weaponHash, component) then + local model = GetWeaponComponentTypeModel(component) + if model ~= 0 then + LoadModel(model) + GiveWeaponComponentToWeaponObject(entity, component) + SetModelAsNoLongerNeeded(component) + end + end + end + end + elseif config.framework == "qb" then + for _, data in pairs(attachments) do + local model = GetWeaponComponentTypeModel(data.component) + if model ~= 0 then + LoadModel(model) + GiveWeaponComponentToWeaponObject(entity, data.component) + SetModelAsNoLongerNeeded(data.component) + else + SetWeaponObjectTintIndex(entity, data.component) + end + end + else + --ESX is suck + for i = 1, #attachments do + local componentData = tgiCore.core.GetWeaponComponent(weaponName, attachments[i]) + if componentData then + local hash = componentData.hash + local model = GetWeaponComponentTypeModel(hash) + if model ~= 0 then + LoadModel(model) + GiveWeaponComponentToWeaponObject(entity, hash) + SetModelAsNoLongerNeeded(hash) + else + SetWeaponObjectTintIndex(entity, hash) + end + end + end + end +end + +function getTgiannAttachments(tgiattachments, weapon) + local invSettings = exports["tgiann-attachment"]:inventoryConfig() + if invSettings then + return tgiattachments + else + local returnVal = nil + local waitCb = true + tgiCore.cbFunction("tgiann-attachment:getAttachment", function(data) + returnVal = data + waitCb = false + end, weapon) + while waitCb do Wait(10) end + return returnVal + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/mf_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/mf_inv.lua new file mode 100644 index 000000000..c8bcde8df --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/mf_inv.lua @@ -0,0 +1,77 @@ +if not config["mf-inventory"] then return end + +local playerJob = "" +local lastItems = {} + +local function getInventoryItems(identifier) + exports["mf-inventory"]:getInventoryItems(identifier, function(items) + lastItems = items + weaponCheck() + end) +end + +RegisterNetEvent('esx:playerLoaded') +AddEventHandler('esx:playerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + getInventoryItems(PlayerData.identifier) +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('esx:addInventoryItem') +AddEventHandler('esx:addInventoryItem', function(job) + local PlayerData = exports["tgiann-core"]:getPlayerData() + if not PlayerData then return end + getInventoryItems(PlayerData.identifier) +end) + +RegisterNetEvent('esx:removeInventoryItem') +AddEventHandler('esx:removeInventoryItem', function(job) + local PlayerData = exports["tgiann-core"]:getPlayerData() + if not PlayerData then return end + getInventoryItems(PlayerData.identifier) +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if item and key == item.name then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved(lastItems) + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and string.find(string.lower(item.name), "weapon") then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.metadata.tgiattachments, joaat(item.name)), + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/origen_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/origen_inv.lua new file mode 100644 index 000000000..aee6b831a --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/origen_inv.lua @@ -0,0 +1,79 @@ +if not config.origen_inventory then return end + +local origen_inventory = exports.origen_inventory +local playerJob = "" +local lastItems = {} + +local function getInventoryItems() + lastItems = origen_inventory:GetInventory() + weaponCheck() +end + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + getInventoryItems() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +--QB +RegisterNetEvent('QBCore:Player:SetPlayerData', function(PlayerData) + playerJob = PlayerData.job.name + getInventoryItems() +end) + +--ESX +RegisterNetEvent('esx:addInventoryItem') +AddEventHandler('esx:addInventoryItem', function() + getInventoryItems() +end) + +RegisterNetEvent('esx:removeInventoryItem') +AddEventHandler('esx:removeInventoryItem', function() + getInventoryItems() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if key == item.info.serie then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and item.type == "weapon" then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.info.serie, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.info.tgiattachments, joaat(item.name)) or item.info.attachments, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/ox_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/ox_inv.lua new file mode 100644 index 000000000..84715b1e3 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/ox_inv.lua @@ -0,0 +1,64 @@ +if not config.ox_inventory then return end + +local playerJob = "" +local lastItems = {} + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + lastItems = exports.ox_inventory:GetPlayerItems() or {} + weaponCheck() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +AddEventHandler('ox_inventory:updateInventory', function(changes) + for i, value in pairs(changes) do + lastItems[i] = value or nil + end + weaponCheck() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if item and key == (item.metadata.serial or item.name) then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and string.find(string.lower(item.name), "weapon") then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.metadata.serial or item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.metadata.tgiattachments, joaat(item.name)) or item.metadata.components, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qb_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qb_inv.lua new file mode 100644 index 000000000..7ee834347 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qb_inv.lua @@ -0,0 +1,64 @@ +if config.framework ~= "qb" then return end +if not config.useDefaultInventory then return end + +local playerJob = "" +local lastItems = {} + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + lastItems = PlayerData?.items + weaponCheck() +end) + +RegisterNetEvent('QBCore:Player:SetPlayerData', function(PlayerData) + playerJob = PlayerData.job.name + lastItems = PlayerData?.items + weaponCheck() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if key == item.info.serie then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and item.type == "weapon" then + self.Functions.AddWeapon({ + weapon = item.name, + key = item.info.serie, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.info.tgiattachments, joaat(item.name)) or item.info.attachments, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qs_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qs_inv.lua new file mode 100644 index 000000000..387313a79 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/qs_inv.lua @@ -0,0 +1,89 @@ +if not config["qs-inventory"] then return end + +local playerJob = "" +local lastItems = {} +local isBussy = false + +local function getInventoryItems() + if isBussy then return end + isBussy = true + while exports['qs-inventory']:inInventory() do Wait(10) end -- getUserInventory not updating when inventory is open + Wait(1000) -- getUserInventory is updating late + lastItems = exports['qs-inventory']:getUserInventory() + weaponCheck(playerJob, true) + isBussy = false +end + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + getInventoryItems() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +--ESX +RegisterNetEvent('esx:addInventoryItem') +AddEventHandler('esx:addInventoryItem', function() + getInventoryItems() +end) + +RegisterNetEvent('esx:removeInventoryItem') +AddEventHandler('esx:removeInventoryItem', function() + getInventoryItems() +end) + +--QB +RegisterNetEvent('QBCore:Player:SetPlayerData', function(PlayerData) + playerJob = PlayerData.job.name + lastItems = PlayerData?.items + weaponCheck() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck(playerJob) +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if item and key == (item.info?.serie or item.name) then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item then + local isWeapon = string.find(string.lower(item.name), "weapon") + debug(item.name .. " Check For Add Weapon", "isWeapon: " .. tostring(isWeapon)) + if isWeapon then + debug(item.name .. " Added") + self.Functions.AddWeapon({ + weapon = item.name, + key = item.info?.serie or item.name, + attachments = config.tgiann_attachments and + getTgiannAttachments(item.info.tgiattachments, joaat(item.name)) or item.info?.attachments, + playerJob = playerJob, + isMale = isMale + }) + end + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/tgiann_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/tgiann_inv.lua new file mode 100644 index 000000000..71e036f7f --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/bridge/tgiann_inv.lua @@ -0,0 +1,62 @@ +if not config["tgiann-inventory"] then return end + +local playerJob = "" +local lastItems = {} + +RegisterNetEvent('tgiCore:Client:OnPlayerLogout', function() + self.Functions.RemoveAllWeapons() +end) + +RegisterNetEvent('tgiCore:Client:OnPlayerLoaded') +AddEventHandler('tgiCore:Client:OnPlayerLoaded', function(PlayerData) + playerJob = PlayerData.job.name + lastItems = exports["tgiann-inventory"]:GetPlayerItems() + weaponCheck() +end) + +RegisterNetEvent('tgiann-inventory:inventoryUpdated') +AddEventHandler('tgiann-inventory:inventoryUpdated', function(items) + lastItems = items + weaponCheck() +end) + +RegisterNetEvent('tgiCore:Client:OnJobUpdate') +AddEventHandler('tgiCore:Client:OnJobUpdate', function(job) + self.Functions.RemoveAllWeapons() + playerJob = job.name + weaponCheck() +end) + +self.Functions.CheckWeaponIsRemoved = function() + if not next(self.weapons) then return end + for key, _ in pairs(self.weapons) do + local success = false + for _, item in pairs(lastItems) do + if key == item?.info?.serie then + success = true + break + end + end + if not success then + self.Functions.RemoveWeapon(key) + end + end +end + +function weaponCheck() + if not lastItems then return end + Wait(100) + self.Functions.CheckWeaponIsRemoved() + local isMale = GetEntityModel(PlayerPedId()) == `mp_m_freemode_01` + for _, item in pairs(lastItems) do + if item and item.type == "weapon" then + self.Functions.AddWeapon({ + weapon = string.gsub(item.name, "_police", ""), + key = item?.info?.serie or item.name, + attachments = item?.info?.tgiattachments or {}, + playerJob = playerJob, + isMale = isMale + }) + end + end +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/client.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/client.lua new file mode 100644 index 000000000..b45246355 Binary files /dev/null and b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/client.lua differ diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/editable.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/editable.lua new file mode 100644 index 000000000..a5f9866b3 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/client/editable.lua @@ -0,0 +1,12 @@ +function canShow(targetPed) + local isEntityVisible = IsEntityVisible(targetPed) + local getEntityAlpha = GetEntityAlpha(targetPed) + local pedIsVisible = isEntityVisible and getEntityAlpha == 255 + debug("pedIsVisible: " .. tostring(pedIsVisible), "ped:" .. targetPed, "isEntityVisible: " .. tostring(isEntityVisible), "getEntityAlpha: " .. getEntityAlpha) + return pedIsVisible +end + +function debug(...) + if not config.debug then return end + print(...) +end diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/config.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/config.lua new file mode 100644 index 000000000..db92c0b04 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/config.lua @@ -0,0 +1,206 @@ +--[[ + - this script needs tgiann-core script to work, you can download the script from your keymaster account + Start tgiann-core script after es_extented/qb-core script and before tgiann-* scripts + Adjust the tgiann-core config file according to the framework you are using + + https://tgiann.gitbook.io/tgiann/scripts/tgiann-weapons-on-back + + Note: If you want the weapons to be invisible while in Noclip, the character must be completely invisible. If your character is invisible to other players, other players cannot see these weapons. + If you want to edit this, take a look at client/editable.lua +]] + +tgiCoreExports = exports["tgiann-core"] +config = tgiCoreExports:getConfig() +config.debug = false + +config.tgiann_attachments = GetResourceState("tgiann-attachment") ~= "missing" -- https://tgiann.tebex.io/package/5399235 + +-- Weapon positions for male and female characters. You can add additional positions here if you like. +config.positions = { + male = { + back = { -- We have set 3 positions for the back. You can add or remove extra positions if you like. This setting also applies to other positions. + { -- position 1 + bone = 24816, + offset = vector3(0.285, -0.17, 0.13), + rot = vector3(0.0, 170.0, 0.0), + }, + { -- position 2 + bone = 24816, + offset = vector3(0.285, -0.17, 0.0), + rot = vector3(0.0, 170.0, 0.0), + }, + { -- position 3 + bone = 24816, + offset = vector3(0.285, -0.17, -0.13), + rot = vector3(0.0, 170.0, 0.0), + } + }, + front = { + { + bone = 24818, + offset = vector3(-0.03, 0.19, 0.0), + rot = vector3(-10.0, 40.0, 5.0), + } + }, + right = { + { + bone = 11816, + offset = vector3(-0.01, 0.02, 0.215), + rot = vector3(-100.0, 60.0, 45.0), + } + }, + rLegBack = { + { + bone = 11816, + offset = vector3(-0.15, -0.11, 0.22), + rot = vector3(0.0, 95.0, 180.0), + } + }, + waist = { + { + bone = 11816, + offset = vector3(-0.07, -0.13, 0.05), + rot = vector3(180.0, -30.0, 10.0), + } + }, + -- You can add extra positions here if you like. + -- The positions you add can be used in the `config.weaponPositions`, `config.weaponGroupPositions` and `config.weaponGroupJobPositions` settings. + --[[ exampleCustomName = { + { + bone = 11816, + offset = vector3(-0.07, -0.13, 0.05), + rot = vector3(180.0, -30.0, 10.0), + } + }, ]] + }, + female = { + back = { + { + bone = 24816, + offset = vector3(0.285, -0.15, 0.13), + rot = vector3(0.0, 170.0, 0.0), + }, + { + bone = 24816, + offset = vector3(0.285, -0.15, 0.0), + rot = vector3(0.0, 170.0, 0.0), + }, + { + bone = 24816, + offset = vector3(0.285, -0.15, -0.13), + rot = vector3(0.0, 170.0, 0.0), + } + }, + front = { + { + bone = 24818, + offset = vector3(-0.03, 0.21, 0.0), + rot = vector3(-10.0, 40.0, 5.0), + } + }, + right = { + { + bone = 11816, + offset = vector3(-0.09, 0.03, 0.18), + rot = vector3(-105.0, 75.0, 45.0), + } + }, + rLegBack = { + { + bone = 11816, + offset = vector3(-0.15, -0.11, 0.22), + rot = vector3(0.0, 95.0, 180.0), + } + }, + waist = { + { + bone = 11816, + offset = vector3(-0.07, -0.09, 0.05), + rot = vector3(180.0, -30.0, 10.0), + } + } + } + +} + +-- Weapons in the list do not appear on the character +config.disabledWeapons = { + weapon_flashlight = true, + weapon_knuckle = true, + weapon_bottle = true, + weapon_snowball = true, +} + +-- adjusts the location of the weapon regardless of its group +config.weaponPositions = { + --weapon_pistol = "right", +} + +-- adjusts the position of the weapon regardless of its group +config.weaponCustomPositions = { + male = { + weapon_bat = { + bone = 24816, + offset = vector3(0.0, -0.15, 0.03), + rot = vector3(0.0, 80.0, 0.0), + } + }, + female = { + weapon_bat = { + bone = 24816, + offset = vector3(0.0, -0.15, 0.03), + rot = vector3(0.0, 80.0, 0.0), + } + } +} + +--"waist" - "back" - "front" - "rigt" - "rLegBack" - "none" +config.weaponGroupPostions = { + [3539449195] = "back", --GROUP_DIGISCANNER + [-37788308] = "rLegBack", --GROUP_FIREEXTINGUISHER + [1175761940] = "none", --GROUP_HACKINGDEVICE + [2725924767] = "back", --GROUP_HEAVY + [-728555052] = "back", --GROUP_MELEE + [3759491383] = "none", --GROUP_METALDETECTOR + [1159398588] = "back", --GROUP_MG + [3493187224] = "none", --GROUP_NIGHTVISION + [431593103] = "none", --GROUP_PARACHUTE + [1595662460] = "none", --GROUP_PETROLCAN + [416676503] = "waist", --GROUP_PISTOL + [970310034] = "back", --GROUP_RIFLE + [860033945] = "back", --GROUP_SHOTGUN + [-957766203] = "front", --GROUP_SMG + [-1212426201] = "back", --GROUP_SNIPER + [690389602] = "none", --GROUP_STUNGUN + [1548507267] = "none", --GROUP_THROWN + [75159441] = "back", --GROUP_TRANQILIZER + [2685387236] = "none", --GROUP_UNARMED +} + +-- weapon locations for jobs +config.weaponGroupJobPostions = { + { + jobs = { "police" }, -- u can add multible job name + postions = { + [3539449195] = "back", --GROUP_DIGISCANNER + [-37788308] = "rLegBack", --GROUP_FIREEXTINGUISHER + [1175761940] = "none", --GROUP_HACKINGDEVICE + [2725924767] = "back", --GROUP_HEAVY + [-728555052] = "back", --GROUP_MELEE + [3759491383] = "none", --GROUP_METALDETECTOR + [1159398588] = "back", --GROUP_MG + [3493187224] = "none", --GROUP_NIGHTVISION + [431593103] = "none", --GROUP_PARACHUTE + [1595662460] = "none", --GROUP_PETROLCAN + [416676503] = "right", --GROUP_PISTOL + [970310034] = "back", --GROUP_RIFLE + [860033945] = "back", --GROUP_SHOTGUN + [-957766203] = "front", --GROUP_SMG + [-1212426201] = "back", --GROUP_SNIPER + [690389602] = "none", --GROUP_STUNGUN + [1548507267] = "none", --GROUP_THROWN + [75159441] = "back", --GROUP_TRANQILIZER + [2685387236] = "none", --GROUP_UNARMED + } + } +} diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/core_inv_config.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/core_inv_config.lua new file mode 100644 index 000000000..be3ce7e4f --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/configs/core_inv_config.lua @@ -0,0 +1,5 @@ +config.enableInv = { + ["content"] = true, + ["primary"] = true, + ["secondry"] = true, +} \ No newline at end of file diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/fxmanifest.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/fxmanifest.lua new file mode 100644 index 000000000..c59b2fef0 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/fxmanifest.lua @@ -0,0 +1,34 @@ +fx_version 'cerulean' +game 'gta5' +version '1.0.1' + +lua54 'yes' + +dependencies { + 'tgiann-core', +} + +escrow_ignore { + 'configs/*.lua', + 'checkInv.lua', + 'client/bridge/*.lua', + 'client/editable.lua', + 'server/bridge/*.lua' +} + +shared_script { + 'configs/*.lua', + 'checkInv.lua', +} + +client_scripts { + 'client/*.lua', + 'client/bridge/*.lua' +} + +server_scripts { + "server/server.lua", + 'server/bridge/*.lua', +} + +dependency '/assetpacks' \ No newline at end of file diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/core_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/core_inv.lua new file mode 100644 index 000000000..b330e546d --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/core_inv.lua @@ -0,0 +1,13 @@ +if not config.core_inventory then return end + +tgiCore.cbFunction('tgiann-weapons-on-back:core_inventory:server:getInventory', function(source, cb) + local src = source + local xPlayer = tgiCore.getPlayer(src) + if not xPlayer then return end + local citizenid = tgiCore.getCid(xPlayer) + local playerItems = {} + for inv, canAdd in pairs(config.enableInv) do + if canAdd then playerItems[inv] = exports['core_inventory']:getInventory(string.format("%s-%s", inv, citizenid)) end + end + cb(playerItems) +end) diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/esx_inv.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/esx_inv.lua new file mode 100644 index 000000000..a59396790 --- /dev/null +++ b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/bridge/esx_inv.lua @@ -0,0 +1,10 @@ +if config.framework ~= "esx" then return end +if not config.useDefaultInventory then return end + +-- esx is very bad, I can't access current inventory data from client +tgiCore.cbFunction('tgiann-weapons-on-back:esx_inv:server:getInventory', function(source, cb) + local src = source + local xPlayer = tgiCore.getPlayer(src) + if not xPlayer then return end + cb(xPlayer.getInventory(), xPlayer.getLoadout()) +end) \ No newline at end of file diff --git a/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/server.lua b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/server.lua new file mode 100644 index 000000000..794191b64 Binary files /dev/null and b/resources/[Developer]/[Nordi]/tgiann-weapons-on-back/server/server.lua differ diff --git a/resources/[standalone]/nordi_tdm/client.lua b/resources/[standalone]/nordi_tdm/client.lua index 60719f10b..b58076c1d 100644 --- a/resources/[standalone]/nordi_tdm/client.lua +++ b/resources/[standalone]/nordi_tdm/client.lua @@ -25,19 +25,6 @@ local function debugPrint(message) print("^2[TDM DEBUG]^7 " .. message) end --- Funktion zum Prüfen, ob eine Waffe eine Airsoft-Waffe ist -function isAirsoftWeapon(weaponHash) - return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft -end - --- Enhanced function to check if a player is in ragdoll state -function isPedInRagdoll(ped) - return IsPedRagdoll(ped) or - IsPedFalling(ped) or - IsPedDiving(ped) or - (not IsPedStill(ped) and IsPedOnFoot(ped) and not IsPedWalking(ped) and not IsPedRunning(ped) and not IsPedSprinting(ped)) -end - -- Helper function to dump tables for debugging function dumpTable(table, indent) if not indent then indent = 0 end @@ -52,6 +39,19 @@ function dumpTable(table, indent) end end +-- Funktion zum Prüfen, ob eine Waffe eine Airsoft-Waffe ist +function isAirsoftWeapon(weaponHash) + return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft +end + +-- Enhanced function to check if a player is in ragdoll state +function isPedInRagdoll(ped) + return IsPedRagdoll(ped) or + IsPedFalling(ped) or + IsPedDiving(ped) or + (not IsPedStill(ped) and IsPedOnFoot(ped) and not IsPedWalking(ped) and not IsPedRunning(ped) and not IsPedSprinting(ped)) +end + -- Events RegisterNetEvent('tdm:updateGamesList', function(games) activeGames = games @@ -791,570 +791,189 @@ CreateThread(function() if inTDM and not isHit then local ped = PlayerPedId() + -- Prüfe, ob der Spieler im Ragdoll-Zustand ist + if -- Prüfe, ob der Spieler im Ragdoll-Zustand ist if isPedInRagdoll(ped) then -- Bestimme den Angreifer (verwende den letzten Angreifer, wenn innerhalb von 3 Sekunden) local attacker = nil - if lastDamager and (GetGameTimer() - -local QBCore = exports['qb-core']:GetCoreObject() - --- Game Management -local activeGames = {} -local gameIdCounter = 1 - --- Debug-Funktion für Konsole -local function debugPrint(message) - print("^2[TDM SERVER]^7 " .. message) -end - --- Events -RegisterNetEvent('tdm:createGame', function(gameName, fieldId, gameType, password) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - - if not Player then - debugPrint("Spielerstellung fehlgeschlagen - Spieler nicht gefunden: " .. src) - return - end - - -- Prüfen ob Spielfeld bereits belegt - for gameId, gameData in pairs(activeGames) do - if gameData.fieldId == fieldId then - debugPrint("Spielerstellung abgelehnt - Feld bereits belegt: " .. fieldId) - TriggerClientEvent('QBCore:Notify', src, 'Dieses Spielfeld ist bereits belegt!', 'error') - return - end - end - - local gameId = 'game_' .. gameIdCounter - gameIdCounter = gameIdCounter + 1 - - activeGames[gameId] = { - id = gameId, - name = gameName, - fieldId = fieldId, - admin = src, - adminName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, - gameType = gameType, - password = password, - hasPassword = password ~= nil, - status = 'waiting', - team1 = {}, - team2 = {}, - score = {team1 = 0, team2 = 0}, - startTime = nil, - maxTime = Config.maxGameTime, - maxHits = Config.maxHits, - playerStats = {} -- Spieler-Statistiken initialisieren - } - - local typeText = gameType == 'public' and 'öffentliches' or 'privates' - TriggerClientEvent('QBCore:Notify', src, 'Dein ' .. typeText .. ' Spiel "' .. gameName .. '" wurde erstellt!', 'success') - - updateGamesListForAll() - debugPrint("Spiel erstellt: " .. gameId .. " von " .. Player.PlayerData.name .. " (Feld: " .. fieldId .. ")") -end) - -RegisterNetEvent('tdm:requestGamesList', function() - local src = source - debugPrint("Spiele-Liste angefordert von: " .. src) - TriggerClientEvent('tdm:updateGamesList', src, activeGames) -end) - -RegisterNetEvent('tdm:requestJoinGame', function(gameId, password) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - - if not Player or not activeGames[gameId] then - debugPrint("Spielbeitritt fehlgeschlagen - Spieler oder Spiel nicht gefunden: " .. src .. ", " .. gameId) - return - end - - local game = activeGames[gameId] - local playerName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname - - debugPrint("Beitrittsanfrage von " .. playerName .. " (ID: " .. src .. ") für Spiel " .. gameId) - - -- Passwort prüfen falls vorhanden - if game.hasPassword and game.password ~= password then - debugPrint("Beitritt abgelehnt - Falsches Passwort") - TriggerClientEvent('QBCore:Notify', src, 'Falsches Passwort!', 'error') - return - end - - -- Spieler bereits im Spiel? - for _, playerId in ipairs(game.team1) do - if playerId == src then - debugPrint("Beitritt abgelehnt - Spieler bereits in Team 1") - TriggerClientEvent('QBCore:Notify', src, 'Du bist bereits in diesem Spiel!', 'error') - return - end - end - for _, playerId in ipairs(game.team2) do - if playerId == src then - debugPrint("Beitritt abgelehnt - Spieler bereits in Team 2") - TriggerClientEvent('QBCore:Notify', src, 'Du bist bereits in diesem Spiel!', 'error') - return - end - end - - -- Max Spieler erreicht? - local currentPlayers = #game.team1 + #game.team2 - local maxPlayers = Config.gameFields[game.fieldId].maxPlayers - - if currentPlayers >= maxPlayers then - debugPrint("Beitritt abgelehnt - Spiel ist voll") - TriggerClientEvent('QBCore:Notify', src, 'Spiel ist voll!', 'error') - return - end - - -- Prüfen ob Admin online ist (für private Spiele) - if game.gameType == 'private' then - local AdminPlayer = QBCore.Functions.GetPlayer(game.admin) - if not AdminPlayer then - debugPrint("Beitritt abgelehnt - Admin nicht online") - TriggerClientEvent('QBCore:Notify', src, 'Der Spiel-Admin ist nicht online!', 'error') - return - end - end - - -- Join Logic basierend auf Spiel Typ - if game.gameType == 'public' then - debugPrint("Öffentliches Spiel - Direkter Beitritt") - joinPlayerToGame(src, gameId) - TriggerClientEvent('QBCore:Notify', src, 'Du bist dem öffentlichen Spiel beigetreten!', 'success') - else - debugPrint("Privates Spiel - Sende Anfrage an Admin") - TriggerClientEvent('tdm:joinRequest', game.admin, gameId, playerName, src) - TriggerClientEvent('QBCore:Notify', src, 'Join-Anfrage gesendet an ' .. game.adminName, 'info') - end -end) - -RegisterNetEvent('tdm:approveJoinRequest', function(gameId, playerId, approved) - local src = source - local game = activeGames[gameId] - - if not game or game.admin ~= src then - debugPrint("Join-Anfrage Bearbeitung fehlgeschlagen - Ungültiges Spiel oder nicht Admin") - return - end - - if approved then - debugPrint("Join-Anfrage genehmigt für Spieler " .. playerId .. " in Spiel " .. gameId) - joinPlayerToGame(playerId, gameId) - TriggerClientEvent('tdm:joinRequestResult', playerId, true, game.name) - else - debugPrint("Join-Anfrage abgelehnt für Spieler " .. playerId .. " in Spiel " .. gameId) - TriggerClientEvent('tdm:joinRequestResult', playerId, false, game.name) - end -end) - -RegisterNetEvent('tdm:leaveGame', function() - local src = source - debugPrint("Spieler " .. src .. " möchte alle Spiele verlassen") - - for gameId, game in pairs(activeGames) do - removePlayerFromGame(src, gameId) - end - - TriggerClientEvent('tdm:leaveGame', src) -end) - -RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam, attackerId) - local victim = source - - if not activeGames[gameId] then - debugPrint("Hit registriert, aber Spiel " .. gameId .. " existiert nicht!") - return - end - - local game = activeGames[gameId] - - debugPrint("Hit registriert - Opfer: " .. victim .. " (Team: " .. victimTeam .. "), Angreifer: " .. (attackerId or "Unbekannt")) - - -- Spieler Stats initialisieren falls nicht vorhanden - if not game.playerStats then - game.playerStats = {} - end - - if not game.playerStats[victim] then - game.playerStats[victim] = {hits = 0, deaths = 0} - end - - -- Wichtig: Nur wenn ein Angreifer identifiziert wurde, zähle den Kill - if attackerId then - if not game.playerStats[attackerId] then - game.playerStats[attackerId] = {hits = 0, deaths = 0} - end - - -- Stats updaten - game.playerStats[victim].deaths = (game.playerStats[victim].deaths or 0) + 1 - game.playerStats[attackerId].hits = (game.playerStats[attackerId].hits or 0) + 1 - - -- Benachrichtigung an den Angreifer senden - TriggerClientEvent('tdm:hitRegistered', attackerId) - debugPrint("Treffer von " .. attackerId .. " gegen " .. victim .. " registriert") - - -- Team Score erhöhen - if victimTeam == 'team1' then - game.score.team2 = game.score.team2 + 1 - debugPrint("Punkt für Team 2 - Neuer Score: " .. game.score.team2) + if lastDamager and (GetGameTimer() - lastDamageTime) < 3000 then + attacker = lastDamager + + -- Prüfe ob die Waffe eine Airsoft-Waffe ist + if isAirsoftWeapon(lastDamageWeapon) then + debugPrint("Ragdoll durch Airsoft-Waffe - Zählt als Tod durch: " .. attacker) + + -- Lokale Stats sofort updaten + playerStats.deaths = playerStats.deaths + 1 + + -- Treffer-Events auslösen + TriggerEvent('tdm:playerHit') + TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, attacker) + + -- Zurücksetzen des letzten Angreifers + lastDamager = nil + lastDamageWeapon = 0 + + -- Warten um mehrfache Auslösung zu verhindern + Wait(1000) + end + end + end else - game.score.team1 = game.score.team1 + 1 - debugPrint("Punkt für Team 1 - Neuer Score: " .. game.score.team1) - end - else - debugPrint("Treffer gegen " .. victim .. " von unbekanntem Angreifer registriert - Kein Punkt vergeben") - end - - TriggerClientEvent('tdm:deathRegistered', victim) - - -- Score an alle Spieler senden - updateScoreForGame(gameId) - - -- Spiel beenden prüfen - if game.score.team1 >= game.maxHits or game.score.team2 >= game.maxHits then - local winnerTeam = game.score.team1 >= game.maxHits and 'team1' or 'team2' - debugPrint("Max Punkte erreicht - Beende Spiel. Gewinner: " .. winnerTeam) - endGame(gameId, winnerTeam) - end -end) - -RegisterNetEvent('tdm:playerDied', function(gameId) - local src = source - debugPrint("Spieler " .. src .. " ist gestorben in Spiel " .. gameId) - removePlayerFromGame(src, gameId) -end) - -RegisterNetEvent('tdm:requestScoreUpdate', function(gameId) - local src = source - - if activeGames[gameId] then - debugPrint("Score-Update angefordert von " .. src .. " für Spiel " .. gameId) - updateScoreForGame(gameId) - else - debugPrint("Score-Update fehlgeschlagen - Spiel " .. gameId .. " nicht gefunden") - end -end) - -RegisterNetEvent('tdm:debugPlayerStats', function(gameId) - local src = source - if activeGames[gameId] and activeGames[gameId].playerStats and activeGames[gameId].playerStats[src] then - local stats = activeGames[gameId].playerStats[src] - debugPrint("Stats für Spieler " .. src .. ": Hits=" .. (stats.hits or 0) .. ", Deaths=" .. (stats.deaths or 0)) - TriggerClientEvent('QBCore:Notify', src, 'Server Stats - Hits: ' .. (stats.hits or 0) .. ', Deaths: ' .. (stats.deaths or 0), 'info') - else - debugPrint("Keine Stats gefunden für Spieler " .. src .. " in Spiel " .. gameId) - TriggerClientEvent('QBCore:Notify', src, 'Keine Stats gefunden!', 'error') - end -end) - --- Funktionen -function joinPlayerToGame(playerId, gameId) - local game = activeGames[gameId] - if not game then - debugPrint("Spielbeitritt fehlgeschlagen - Spiel nicht gefunden: " .. gameId) - return - end - - -- Team mit weniger Spielern wählen - local team = #game.team1 <= #game.team2 and 'team1' or 'team2' - - table.insert(game[team], playerId) - - -- Spieler-Stats initialisieren - if not game.playerStats then - game.playerStats = {} - end - - game.playerStats[playerId] = {hits = 0, deaths = 0} - - -- Spiel starten wenn mindestens 2 Spieler - if #game.team1 + #game.team2 >= 2 and game.status == 'waiting' then - game.status = 'active' - game.startTime = os.time() - debugPrint("Spiel " .. gameId .. " gestartet - Mindestens 2 Spieler erreicht") - - -- Game Timer starten - startGameTimer(gameId) - end - - TriggerClientEvent('tdm:joinGame', playerId, gameId, team, game.fieldId) - updateScoreForGame(gameId) - updateGamesListForAll() - - debugPrint("Spieler " .. playerId .. " ist Spiel " .. gameId .. " beigetreten (Team: " .. team .. ")") -end - -function removePlayerFromGame(playerId, gameId) - local game = activeGames[gameId] - if not game then return end - - -- Spieler aus Teams entfernen - local removed = false - - for i, id in ipairs(game.team1) do - if id == playerId then - table.remove(game.team1, i) - debugPrint("Spieler " .. playerId .. " aus Team 1 entfernt") - removed = true - break + Wait(500) end end - - if not removed then - for i, id in ipairs(game.team2) do - if id == playerId then - table.remove(game.team2, i) - debugPrint("Spieler " .. playerId .. " aus Team 2 entfernt") - removed = true - break - end - end - end - - if not removed then - debugPrint("Spieler " .. playerId .. " nicht in Spiel " .. gameId .. " gefunden") - return - end - - -- Wenn Admin das Spiel verlässt, Spiel beenden - if game.admin == playerId then - debugPrint("Admin hat das Spiel verlassen - Beende Spiel " .. gameId) - endGame(gameId, nil, 'Admin hat das Spiel verlassen') - return - end - - checkGameEnd(gameId) - updateGamesListForAll() -end +end) -function endGame(gameId, winnerTeam, reason) - local game = activeGames[gameId] - if not game then - debugPrint("Spielende fehlgeschlagen - Spiel nicht gefunden: " .. gameId) - return - end - - game.status = 'finished' - - local allPlayers = {} - for _, playerId in ipairs(game.team1) do - table.insert(allPlayers, playerId) - end - for _, playerId in ipairs(game.team2) do - table.insert(allPlayers, playerId) - end - - -- Game End Event an alle Spieler - for _, playerId in ipairs(allPlayers) do - debugPrint("Sende Spielende-Event an Spieler " .. playerId) - TriggerClientEvent('tdm:gameEnded', playerId, winnerTeam, game.score.team1, game.score.team2) - end - - -- Spiel nach 10 Sekunden löschen - SetTimeout(10000, function() - activeGames[gameId] = nil - updateGamesListForAll() - debugPrint("Spiel " .. gameId .. " aus der Liste entfernt") - end) - - if reason then - debugPrint("Spiel " .. gameId .. " beendet: " .. reason) - else - debugPrint("Spiel " .. gameId .. " beendet. Gewinner: " .. (winnerTeam or "Unentschieden")) - end -end - -function startGameTimer(gameId) - CreateThread(function() - local game = activeGames[gameId] - if not game then return end - - local maxTime = game.maxTime - local startTime = os.time() - - debugPrint("Timer für Spiel " .. gameId .. " gestartet. Maximale Zeit: " .. maxTime .. " Sekunden") - - while game and game.status == 'active' and (os.time() - startTime) < maxTime do - Wait(1000) - game = activeGames[gameId] -- Refresh game data +-- Direkter Waffen-Schaden Monitor für zusätzliche Zuverlässigkeit +CreateThread(function() + while true do + Wait(0) + if inTDM and not isHit then + local ped = PlayerPedId() - -- Alle 30 Sekunden Debug-Info - if (os.time() - startTime) % 30 == 0 then - debugPrint("Spiel " .. gameId .. " läuft seit " .. (os.time() - startTime) .. " Sekunden") + -- Prüfe auf Projektil-Treffer + if HasEntityBeenDamagedByWeapon(ped, 0, 2) then -- 2 = Projektilwaffen + debugPrint("Projektil-Treffer erkannt") + + -- Schaden zurücksetzen + ClearEntityLastDamageEntity(ped) + ClearPedLastWeaponDamage(ped) + SetEntityHealth(ped, GetEntityMaxHealth(ped)) + + -- Lokale Stats sofort updaten + playerStats.deaths = playerStats.deaths + 1 + + -- Treffer-Events auslösen + TriggerEvent('tdm:playerHit') + TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, lastDamager) + + -- Warten um mehrfache Auslösung zu verhindern + Wait(500) end + else + Wait(500) end - - -- Zeit abgelaufen - if game and game.status == 'active' then - local winnerTeam = game.score.team1 > game.score.team2 and 'team1' or - game.score.team2 > game.score.team1 and 'team2' or nil - debugPrint("Spielzeit abgelaufen - Beende Spiel " .. gameId) - endGame(gameId, winnerTeam, 'Zeit abgelaufen') - end - end) -end - -function checkGameEnd(gameId) - local game = activeGames[gameId] - if not game then return end - - local totalPlayers = #game.team1 + #game.team2 - - if totalPlayers < 2 and game.status == 'active' then - debugPrint("Zu wenig Spieler - Beende Spiel " .. gameId) - endGame(gameId, nil, 'Zu wenig Spieler') - elseif totalPlayers == 0 then - activeGames[gameId] = nil - updateGamesListForAll() - debugPrint("Spiel " .. gameId .. " gelöscht (keine Spieler)") - end -end - -function updateScoreForGame(gameId) - local game = activeGames[gameId] - if not game then - debugPrint("Score-Update fehlgeschlagen - Spiel nicht gefunden: " .. gameId) - return - end - - debugPrint("Score Update für Spiel " .. gameId .. ": Team1=" .. game.score.team1 .. ", Team2=" .. game.score.team2) - - for _, playerId in ipairs(game.team1) do - local playerStats = game.playerStats[playerId] or {hits = 0, deaths = 0} - TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, { - hits = playerStats.hits or 0, - deaths = playerStats.deaths or 0 - }) - end - - for _, playerId in ipairs(game.team2) do - local playerStats = game.playerStats[playerId] or {hits = 0, deaths = 0} - TriggerClientEvent('tdm:updateScore', playerId, game.score.team1, game.score.team2, { - hits = playerStats.hits or 0, - deaths = playerStats.deaths or 0 - }) - end -end - -function updateGamesListForAll() - local players = QBCore.Functions.GetPlayers() - for _, playerId in pairs(players) do - TriggerClientEvent('tdm:updateGamesList', playerId, activeGames) - end -end - --- Player Disconnect Handler -AddEventHandler('playerDropped', function() - local src = source - - for gameId, game in pairs(activeGames) do - removePlayerFromGame(src, gameId) - end - - debugPrint("Spieler " .. src .. " hat den Server verlassen - aus allen Spielen entfernt") -end) - --- Server Start - Games Liste leeren -AddEventHandler('onResourceStart', function(resourceName) - if GetCurrentResourceName() == resourceName then - activeGames = {} - gameIdCounter = 1 - debugPrint("TeamDeathmatch System gestartet!") end end) +-- Function to spawn NPCs for all fields +function spawnFieldNPCs() + for fieldId, fieldData in pairs(Config.gameFields) do + if fieldData.lobby and fieldData.lobby.npc then + local npcData = fieldData.lobby.npc + local model = GetHashKey(npcData.model) + + -- Request the model + RequestModel(model) + while not HasModelLoaded(model) do + Wait(10) + end + + -- Create the NPC + local npc = CreatePed(4, model, npcData.coords.x, npcData.coords.y, npcData.coords.z, npcData.coords.w, false, true) + SetEntityAsMissionEntity(npc, true, true) + SetBlockingOfNonTemporaryEvents(npc, true) + FreezeEntityPosition(npc, true) + SetEntityInvincible(npc, true) + + -- Add to spawned NPCs table + table.insert(spawnedNPCs, npc) + + -- Add target interaction + exports['qb-target']:AddTargetEntity(npc, { + options = { + { + type = "client", + event = "tdm:openMenu", + icon = "fas fa-gamepad", + label = "TeamDeathmatch Menu", + fieldId = fieldId + } + }, + distance = 2.0 + }) + + debugPrint("Spawned NPC for field: " .. fieldId) + end + end +end + +-- Register event handler for NPC interaction +RegisterNetEvent('tdm:openMenu', function(data) + if data.fieldId then + openMainMenu(data.fieldId) + end +end) + +-- Spawn NPCs when resource starts +CreateThread(function() + Wait(1000) -- Wait for everything to load + spawnFieldNPCs() +end) + +-- Function to create blips for all TDM lobbies +function createTDMBlips() + for fieldId, fieldData in pairs(Config.gameFields) do + if fieldData.lobby and fieldData.lobby.pos then + local blip = AddBlipForCoord(fieldData.lobby.pos.x, fieldData.lobby.pos.y, fieldData.lobby.pos.z) + SetBlipSprite(blip, 156) -- You can change this to any appropriate sprite + SetBlipDisplay(blip, 4) + SetBlipScale(blip, 0.8) + SetBlipColour(blip, 3) + SetBlipAsShortRange(blip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("TDM: " .. fieldData.name) + EndTextCommandSetBlipName(blip) + + table.insert(tdmBlips, blip) + debugPrint("Created blip for TDM field: " .. fieldId) + end + end +end + +-- Call this function when the resource starts +CreateThread(function() + Wait(2000) -- Wait for everything to load + createTDMBlips() +end) + +-- Cleanup function +function cleanupResources() + -- Remove NPCs + for _, npc in ipairs(spawnedNPCs) do + if DoesEntityExist(npc) then + DeleteEntity(npc) + end + end + spawnedNPCs = {} + + -- Remove blips + for _, blip in ipairs(tdmBlips) do + if DoesBlipExist(blip) then + RemoveBlip(blip) + end + end + tdmBlips = {} + + debugPrint("Cleaned up all NPCs and blips") +end + +-- Register cleanup when resource stops AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then - -- Alle Spieler aus TDM entfernen - for gameId, game in pairs(activeGames) do - local allPlayers = {} - for _, playerId in ipairs(game.team1) do - table.insert(allPlayers, playerId) - end - for _, playerId in ipairs(game.team2) do - table.insert(allPlayers, playerId) - end - - for _, playerId in ipairs(allPlayers) do - TriggerClientEvent('tdm:leaveGame', playerId) - end - end - - activeGames = {} - debugPrint("TeamDeathmatch System gestoppt!") + cleanupResources() end end) --- Admin-Befehle -RegisterCommand('tdmreset', function(source, args) - local src = source - if src > 0 then -- Spieler - local Player = QBCore.Functions.GetPlayer(src) - if not Player or not Player.PlayerData.job or Player.PlayerData.job.name ~= 'admin' then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung!', 'error') - return +-- Helper function for table.count +if not table.count then + table.count = function(tbl) + local count = 0 + for _, _ in pairs(tbl) do + count = count + 1 end + return count end - - -- Alle Spieler aus TDM entfernen - for gameId, game in pairs(activeGames) do - local allPlayers = {} - for _, playerId in ipairs(game.team1) do - table.insert(allPlayers, playerId) - end - for _, playerId in ipairs(game.team2) do - table.insert(allPlayers, playerId) - end - - for _, playerId in ipairs(allPlayers) do - TriggerClientEvent('tdm:leaveGame', playerId) - end - end - - activeGames = {} - gameIdCounter = 1 - - debugPrint("TeamDeathmatch System zurückgesetzt!") - - if src > 0 then - TriggerClientEvent('QBCore:Notify', src, 'TDM System zurückgesetzt!', 'success') - end -end, true) - --- Debug-Befehl für Server-Status -RegisterCommand('tdmstatus', function(source, args) - local src = source - if src > 0 then -- Spieler - local Player = QBCore.Functions.GetPlayer(src) - if not Player or not Player.PlayerData.job or Player.PlayerData.job.name ~= 'admin' then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung!', 'error') - return - end - end - - debugPrint("=== TDM STATUS ===") - debugPrint("Aktive Spiele: " .. table.count(activeGames)) - - for gameId, game in pairs(activeGames) do - debugPrint("Spiel: " .. gameId .. " - " .. game.name) - debugPrint(" Status: " .. game.status) - debugPrint(" Feld: " .. game.fieldId) - debugPrint(" Team 1: " .. #game.team1 .. " Spieler, Score: " .. game.score.team1) - debugPrint(" Team 2: " .. #game.team2 .. " Spieler, Score: " .. game.score.team2) - end - - if src > 0 then - TriggerClientEvent('QBCore:Notify', src, 'TDM Status in Server-Konsole', 'info') - end -end, true) - --- Hilfsfunktion für table.count -table.count = function(tbl) - local count = 0 - for _, _ in pairs(tbl) do - count = count + 1 - end - return count end diff --git a/resources/[standalone]/nordi_tdm/server.lua b/resources/[standalone]/nordi_tdm/server.lua index 1c4756156..0c52224f1 100644 --- a/resources/[standalone]/nordi_tdm/server.lua +++ b/resources/[standalone]/nordi_tdm/server.lua @@ -183,28 +183,30 @@ RegisterNetEvent('tdm:playerWasHit', function(gameId, victimTeam, attackerId) game.playerStats[victim] = {hits = 0, deaths = 0} end - if attackerId and not game.playerStats[attackerId] then - game.playerStats[attackerId] = {hits = 0, deaths = 0} - end - - -- Stats updaten - game.playerStats[victim].deaths = (game.playerStats[victim].deaths or 0) + 1 - + -- Wichtig: Nur wenn ein Angreifer identifiziert wurde, zähle den Kill if attackerId then + if not game.playerStats[attackerId] then + game.playerStats[attackerId] = {hits = 0, deaths = 0} + end + + -- Stats updaten + game.playerStats[victim].deaths = (game.playerStats[victim].deaths or 0) + 1 game.playerStats[attackerId].hits = (game.playerStats[attackerId].hits or 0) + 1 + + -- Benachrichtigung an den Angreifer senden TriggerClientEvent('tdm:hitRegistered', attackerId) debugPrint("Treffer von " .. attackerId .. " gegen " .. victim .. " registriert") + + -- Team Score erhöhen + if victimTeam == 'team1' then + game.score.team2 = game.score.team2 + 1 + debugPrint("Punkt für Team 2 - Neuer Score: " .. game.score.team2) + else + game.score.team1 = game.score.team1 + 1 + debugPrint("Punkt für Team 1 - Neuer Score: " .. game.score.team1) + end else - debugPrint("Treffer gegen " .. victim .. " von unbekanntem Angreifer registriert") - end - - -- Team Score erhöhen - if victimTeam == 'team1' then - game.score.team2 = game.score.team2 + 1 - debugPrint("Punkt für Team 2 - Neuer Score: " .. game.score.team2) - else - game.score.team1 = game.score.team1 + 1 - debugPrint("Punkt für Team 1 - Neuer Score: " .. game.score.team1) + debugPrint("Treffer gegen " .. victim .. " von unbekanntem Angreifer registriert - Kein Punkt vergeben") end TriggerClientEvent('tdm:deathRegistered', victim)