Scripts/resources/[custom_script]/keep-companion/server/server.lua
2024-12-29 20:48:41 +01:00

682 lines
23 KiB
Lua

local QBCore = exports['qb-core']:GetCoreObject()
-- pet system
local maxLimit = Config.MaxActivePetsPetPlayer
-- ============================
-- Class
-- ============================
Pet = {
players = {}
}
--- search for player(source) item's hash inside Pet list
function Pet:isSpawned(source, item)
if self.players[source] ~= nil then
for key, table in pairs(self.players[source]) do
if item.info.hash == key then
return true
end
end
end
return false
end
--- add pet to Pet table
function Pet:setAsSpawned(source, o)
self.players[source] = self.players[source] or {}
self.players[source][o.item.info.hash] = self.players[source][o.item.info.hash] or {}
self.players[source][o.item.info.hash].model = o.model
self.players[source][o.item.info.hash].entity = o.entity
-- memmory new data saving method
self.players[source][o.item.info.hash].name = o.item.name
self.players[source][o.item.info.hash].info = o.item.info
return true
end
--- removes pet from Pet table
function Pet:setAsDespawned(source, item)
self.players[source] = self.players[source] or {}
self.players[source][item.info.hash] = nil
end
--- start spawn chain
function Pet:spawnPet(source, model, item)
-- sumun pet when it does exist inside our database
local isSpawned = Pet:isSpawned(source, item)
if isSpawned == true then
-- depsawn ped
Pet:despawnPet(source, item, nil)
return
end
local limit = Pet:isMaxLimitPedReached(source)
if limit == true then
TriggerClientEvent('QBCore:Notify', source, string.format(Lang:t('error.reached_max_allowed_pet'), maxLimit),
'error', 2500)
return
end
local Player = QBCore.Functions.GetPlayer(source)
-- spawn ped
if item.weight == 500 then
-- need inital values
if Player.PlayerData.items[item.slot] then
Player.PlayerData.items[item.slot].weight = math.random(1000, 4500)
end
Player.Functions.SetInventory(Player.PlayerData.items, true)
end
if item.info.health <= 100 and item.info.health ~= 0 then
-- prevent 100 to stuck in data as health!
if Player.PlayerData.items[item.slot] then
Player.PlayerData.items[item.slot].info.health = 0
end
Player.Functions.SetInventory(Player.PlayerData.items, true)
return
end
local owner = not (item.info.owner.phone == Player.PlayerData.charinfo.phone)
TriggerClientEvent('keep-companion:client:callCompanion', source, model, owner, item)
end
RegisterNetEvent('keep-companion:server:despwan_not_owned_pet', function(hash)
Pet:despawnPet(source, { info = {
hash = hash
} }, true)
end)
--- check if player reached maximum allowed pet
function Pet:isMaxLimitPedReached(source)
local count = 0
if self.players[source] == nil then
return false
else
for _ in pairs(self.players[source]) do
count = count + 1
end
if count == 0 then
return false
elseif count >= maxLimit then
return true
end
end
end
--- despawn pet and remove it's data from server
function Pet:despawnPet(source, item, revive)
-- despawn pet
-- save all data after despawning pet
TriggerClientEvent('keep-companion:client:despawn', source, item, revive)
end
function Pet:findbyhash(source, hash)
for key, value in pairs(self.players[source]) do
if key == hash then
return value
end
end
return false
end
local server_saving_interval = 5000
local server_saving_interval_sec = math.floor(server_saving_interval / 1000)
local day = 10
local max_age = 60 * 60 * 24 * day
function Pet:save_all_info(source, hash)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
local petData = Pet:findbyhash(source, hash)
local items = Player.Functions.GetItemsByName(petData.name)
local slot = nil
-- find item
for key, pet_item in pairs(items) do
if pet_item.info.hash == hash then
slot = pet_item.slot
break
end
end
if slot == nil then return end
-- skip saving data if pet aleady dead
if petData.info.health == 0 then return end
if petData.info.health > 100 then
if petData.info.age >= max_age then
return
end
-- increase pet age when it didnt reached max age
petData.info.age = petData.info.age + (server_saving_interval_sec)
Update:food(petData, 'decrease')
Update:thirst(petData, 'increase')
else
petData.info.health = 0
TriggerClientEvent('keep-companion:client:forceKill', source, hash, 'hunger')
end
if Player.PlayerData.items[slot] then
petData.info.health = Round(petData.info.health, 2) -- round values
petData.info.thirst = Round(petData.info.thirst, 2)
petData.info.food = Round(petData.info.food, 2)
Player.PlayerData.items[slot].info = petData.info
end
Player.Functions.SetInventory(Player.PlayerData.items, true)
end
RegisterNetEvent('keep-companion:server:setAsDespawned', function(item)
if item == nil then return end
Pet:setAsDespawned(source, item)
end)
-- ============================
-- Items
-- ============================
local core_items = Config.core_items
local function remove_item(src, Player, name, amount)
local res = Player.Functions.RemoveItem(name, amount)
if res then
TriggerClientEvent('ps-inventory:client:ItemBox', src, QBCore.Shared.Items[name], "remove")
end
return res
end
-- food
QBCore.Functions.CreateUseableItem(core_items.food.item_name, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
TriggerClientEvent('keep-companion:client:start_feeding_animation', source)
end)
RegisterNetEvent('keep-companion:server:increaseFood', function(item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil or item == nil then return end
if not remove_item(source, Player, Config.core_items.food.item_name, 1) then
TriggerClientEvent('QBCore:Notify', source, 'Kunne ikke fjernes fra dit inventar', 'error', 2500)
return
end
local petData = Pet:findbyhash(source, item.info.hash)
petData.info.food = petData.info.food + 50
TriggerClientEvent('QBCore:Notify', source, 'Dit kæledyr blev fodret! Vent lidt for at det har en effekt', 'success', 2500)
end)
-- change owenership
QBCore.Functions.CreateUseableItem(core_items.collar.item_name, function(source, item)
TriggerClientEvent('keep-companion:client:collar_process', source)
end)
-- rename - name tag
QBCore.Functions.CreateUseableItem(core_items.nametag.item_name, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil or item == nil then return end
TriggerClientEvent('keep-companion:client:rename_name_tag', source, item)
end)
RegisterNetEvent('keep-companion:server:rename_name_tag', function(name)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
if not remove_item(source, Player, Config.core_items.nametag.item_name, 1) then
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.failed_to_remove_item_from_inventory'), 'error', 2500)
return
end
TriggerClientEvent("keep-companion:client:rename_name_tagAction", source, name)
end)
-- first aid - revive
QBCore.Functions.CreateUseableItem(core_items.firstaid.item_name, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
end)
QBCore.Functions.CreateUseableItem(core_items.groomingkit.item_name, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
TriggerClientEvent('keep-companion:client:start_grooming_process', source)
end)
RegisterNetEvent('keep-companion:server:grooming_process', function(item)
local pet_information = find_pet_model_by_item_name(item.name)
local information = {
pet_variation_list = PetVariation:getPedVariationsNameList(pet_information.model),
pet_information = pet_information,
disable = {
rename = true
},
type = Config.core_items.groomingkit.item_name
}
TriggerClientEvent('keep-companion:client:initialization_process', source, item, information)
end)
local function save_info_waterbottle(Player, item, amount)
if Player.PlayerData.items[item.slot] then
Player.PlayerData.items[item.slot].info.liter = amount
end
Player.Functions.SetInventory(Player.PlayerData.items, true)
end
local function initialize_info_waterbottle(Player, item)
if Player.PlayerData.items[item.slot] then
Player.PlayerData.items[item.slot].info = {}
Player.PlayerData.items[item.slot].info.liter = 0
end
Player.Functions.SetInventory(Player.PlayerData.items, true)
end
local function fillwater_bottle(source, item)
local Player = QBCore.Functions.GetPlayer(source)
if Player == nil then return end
local max_c = Config.core_items.waterbottle.settings.max_capacity
local water_bottle_refill_value = Config.core_items.waterbottle.settings.water_bottle_refill_value
local amount = 0
if type(item.info) ~= "table" or (type(item.info) == "table" and item.info.liter == nil) then
initialize_info_waterbottle(Player, item)
TriggerClientEvent('QBCore:Notify', source, 'Vasker vandflaske!', 'primary', 2500)
return
end
if item.info.liter == nil then
-- backup initialization
initialize_info_waterbottle(Player, item)
TriggerClientEvent('QBCore:Notify', source, 'Vasker vandflaske!', 'primary', 2500)
return
end
if item.info.liter > max_c then
TriggerClientEvent('QBCore:Notify', source, 'Er allerede på maks kapacitet', 'error', 2500)
return
elseif item.info.liter == max_c then
amount = max_c
TriggerClientEvent('QBCore:Notify', source, 'Genopfylder allerede. Fyldt flaske har ingen effekt på kapacitet',
'error', 2500)
else
amount = item.info.liter + water_bottle_refill_value
if amount >= max_c then
amount = max_c
end
end
if type(amount) ~= "number" then
TriggerClientEvent('QBCore:Notify', source, 'Kunne ikke få mængde', 'error', 2500)
return
end
save_info_waterbottle(Player, item, amount)
TriggerClientEvent('QBCore:Notify', source, 'Flaske fyldt', 'success', 2500)
end
QBCore.Functions.CreateUseableItem(core_items.waterbottle.item_name, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
local water_bottle_refill_value = Config.core_items.waterbottle.settings.water_bottle_refill_value
if Player == nil then return end
if not remove_item(source, Player, 'water_bottle', water_bottle_refill_value) then
local msg = Lang:t('error.not_enough_water_bottles')
msg = string.format(msg, water_bottle_refill_value)
TriggerClientEvent('QBCore:Notify', source, msg, 'error', 2500)
return
end
TriggerClientEvent('keep-companion:client:filling_animation', source, item)
end)
RegisterNetEvent('keep-companion:server:filling_event', function(item)
fillwater_bottle(source, item)
end)
QBCore.Functions.CreateCallback('keep-companion:server:decrease_thirst', function(source, cb, data)
local player = QBCore.Functions.GetPlayer(source)
local pet_water_bottle = player.Functions.GetItemByName(Config.core_items.waterbottle.item_name)
if pet_water_bottle.info == nil then
TriggerClientEvent('QBCore:Notify', source, 'Du bør vaske falsken først!', 'error', 2500)
print('issue with nill info: https://github.com/swkeep/keep-companion/issues/25')
return
end
if pet_water_bottle.info.liter == nil then
TriggerClientEvent('QBCore:Notify', source, 'Du bør vaske falsken først!', 'error', 2500)
print('maybe use your water bottle when there is some water_bottle s in your inventory')
print('developer: issue with nill info -> liter: https://github.com/swkeep/keep-companion/issues/25')
return
end
pet_water_bottle.info.liter = pet_water_bottle.info.liter -
Config.core_items.waterbottle.settings.water_bottle_refill_value
if pet_water_bottle.info.liter < 0 then
TriggerClientEvent('QBCore:Notify', source, Lang:t('error.not_enough_water_in_your_bottle'), 'error', 2500)
return
end
local petData = Pet:findbyhash(source, data.info.hash)
local t_r_p_d = Config.core_items.waterbottle.settings.thirst_reduction_per_drinking
if not pet_water_bottle then cb(false) return end
if petData.info.thirst < 0 then
petData.info.thirst = 0
end
if petData.info.thirst <= t_r_p_d then
petData.info.thirst = 0
else
petData.info.thirst = petData.info.thirst - t_r_p_d
end
TriggerClientEvent('QBCore:Notify', source, Lang:t('success.successful_drinking'), 'success', 2500)
save_info_waterbottle(player, pet_water_bottle, pet_water_bottle.info.liter)
end)
RegisterNetEvent('keep-companion:server:revivePet', function(item, process_type)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
local petData = Pet:findbyhash(source, item.itemData.info.hash)
local heal_amount = Config.core_items.firstaid.settings.heal_amount
local revive_bonuses = Config.core_items.firstaid.settings.revive_heal_bonuses
local pet_maxHealth = getMaxHealth(item.model)
local potential_heal_amount = math.floor(pet_maxHealth * (heal_amount / 100))
local msg = ''
if not petData then
TriggerClientEvent('QBCore:Notify', src, Lang:t('error.failed_to_start_procces') .. process_type, 'primary', 2500)
return
end
if petData.info.health >= pet_maxHealth then
-- pet has more than life more than correct max life rewrite wrong value
petData.info.health = pet_maxHealth
TriggerClientEvent('QBCore:Notify', src, Lang:t('info.full_life_pet'), 'primary', 2500)
return
end
if not remove_item(source, Player, Config.core_items.firstaid.item_name, 1) then
TriggerClientEvent('QBCore:Notify', src, 'Kunne ikke fjernes fra inventar', 'error', 2500)
return
end
if process_type and process_type == 'Heal' then
local res = math.floor(petData.info.health + potential_heal_amount)
petData.info.health = res
if petData.info.health >= pet_maxHealth then
petData.info.health = pet_maxHealth
end
Pet:save_all_info(src, item.itemData.info.hash)
msg = Lang:t('success.healing_was_successful')
msg = string.format(msg, petData.info.health, pet_maxHealth)
TriggerClientEvent('keep-companion:client:update_health_value', src, item, petData.info.health)
TriggerClientEvent('QBCore:Notify', src, msg, 'success', 2500)
return
end
petData.info.health = 100 + revive_bonuses
Pet:save_all_info(src, item.itemData.info.hash) -- save pet's data
Pet:despawnPet(src, petData, true) -- despawn dead pet
msg = Lang:t('success.successful_revive')
msg = string.format(msg, item.itemData.info.name)
TriggerClientEvent('QBCore:Notify', src, msg, 'success', 2500)
end)
--- get pet max health from confing file by it's model
function getMaxHealth(model)
for key, value in pairs(Config.pets) do
if value.model == model then
return value.maxHealth
end
end
end
-- all pets
for key, value in pairs(Config.pets) do
QBCore.Functions.CreateUseableItem(value.name, function(source, item)
if item.name ~= value.name then return end
local model = value.model
-- need inital values
if type(item.info) ~= "table" or (type(item.info) == "table" and item.info.hash == nil) then
-- init companion
initItem(source, item)
TriggerClientEvent('QBCore:Notify', source, Lang:t('success.pet_initialization_was_successful'), 'success',
2500)
return
end
local cooldown = PlayersCooldown:isOnCooldown(source)
if cooldown > 0 then
local msg = Lang:t('info.still_on_cooldown')
msg = string.format(msg, (cooldown / 1000))
TriggerClientEvent('QBCore:Notify', source, msg, 'primary', 2500)
return
end
Pet:spawnPet(source, model, item)
end)
end
local function search_inventory(cid)
local Player = QBCore.Functions.GetPlayer(cid)
if not Player then return false end
local count = 0
for k, illegal_item in pairs(Config.k9.illegal_items) do
local item = Player.Functions.GetItemByName(illegal_item)
if item then
count = count + 1
if count > 0 then
return true
end
end
end
return false
end
QBCore.Functions.CreateCallback('keep-companion:server:search_inventory', function(source, cb, cid)
local res = search_inventory(cid)
if not res then
TriggerClientEvent('QBCore:Notify', source, 'K9 fandt intet!', 'error', 2500)
cb(res)
return
end
cb(res)
end)
local function search_vehicle(Type, plate)
local illegal_items = Config.k9.illegal_items
local items_list = nil
if Type == 1 then
items_list = exports[Config.inventory_name]:getGloveboxes(plate)
elseif Type == 2 then
items_list = exports[Config.inventory_name]:getTruck(plate)
end
if items_list then
for key, item in pairs(items_list.items) do
for k, i_name in pairs(illegal_items) do
if item.name == i_name then
return true
end
end
end
end
return false
end
QBCore.Functions.CreateCallback('keep-companion:server:search_vehicle', function(source, cb, data)
local res = search_vehicle(data.key, data.plate)
if not res then
TriggerClientEvent('QBCore:Notify', source, 'K9 fandt intet!', 'error', 2500)
cb(res)
return
end
TriggerClientEvent('QBCore:Notify', source, 'K9 fandt noget!', 'success', 2500)
cb(res)
end)
-- ================================================
-- Item - Updating Information
-- ================================================
function FindWhereIsItem(Player, item, source)
if Player.PlayerData.items == nil or next(Player.PlayerData.items) == nil then
TriggerClientEvent('QBCore:Notify', source, "Ingen ting i inventar!")
return false
end
for k, v in pairs(Player.PlayerData.items) do
if Player.PlayerData.items[k] ~= nil then
if Player.PlayerData.items[k].info.hash == item.hash then
local slot = Player.PlayerData.items[k].slot
return Player.PlayerData.items[slot]
end
end
end
TriggerClientEvent('QBCore:Notify', source, "Kunne ikke finde kæledyr")
return false
end
RegisterNetEvent('keep-companion:server:updateAllowedInfo', function(item, data)
if type(data) ~= "table" or next(data) == nil then return end
local Player = QBCore.Functions.GetPlayer(source)
local current_pet_data = Pet:findbyhash(source, item.hash)
if current_pet_data == nil or current_pet_data == false then return end
local requestedItem = Player.Functions.GetItemsByName(current_pet_data.name)
if type(requestedItem) == "table" then
for key, pet_item in pairs(requestedItem) do
if pet_item.info.hash == item.hash then
requestedItem = pet_item
end
end
if requestedItem == false then return end
end
if data.key == 'XP' then
Update:xp(source, current_pet_data)
return
end
Update:health(source, data, current_pet_data)
end)
QBCore.Functions.CreateCallback('keep-companion:server:renamePet', function(source, cb, item)
local player = QBCore.Functions.GetPlayer(source)
local current_pet_data = Pet:findbyhash(source, item.hash)
-- sanity check
if player == nil or current_pet_data == nil or current_pet_data == false or type(item.name) ~= "string" then
local msg = Lang:t('error.failed_to_rename')
msg = string.format(msg, item.name)
TriggerClientEvent('QBCore:Notify', source, msg, 'error')
cb(false)
return
end
if current_pet_data.info.name == item.content then
local msg = Lang:t('error.failed_to_rename_same_name')
msg = string.format(msg, item.name)
TriggerClientEvent('QBCore:Notify', source, msg, 'error')
cb(false)
return
end
current_pet_data.info.name = item.name
Pet:save_all_info(source, item.hash) -- save name outside loop
-- despawn pet to save name
Pet:despawnPet(source, { info = {
hash = item.hash
} }, true)
cb(item.name)
end)
-- saving thread
CreateThread(function()
-- #TODO add check to table changes
while true do
for source, activePets in pairs(Pet.players) do
if next(activePets) ~= nil then
for hash, petData in pairs(activePets) do
Pet:save_all_info(source, hash)
end
end
end
Wait(server_saving_interval)
end
end)
QBCore.Functions.CreateCallback('keep-companion:server:updatePedData', function(source, cb, clientRes)
local player = QBCore.Functions.GetPlayer(source)
if player == nil then
cb(false)
return
end
if Pet:setAsSpawned(source, clientRes) then
cb(true)
return
end
cb(false)
end)
RegisterNetEvent('keep-companion:server:onPlayerUnload', function(items)
-- save pet's Information when player logout
for key, value in pairs(items) do
Pet:setAsDespawned(source, value)
end
end)
-- ============================
-- Commands
-- ============================
QBCore.Commands.Add('addpet', 'Tilføj kæledyr til inventar (Admin)', {}, false, function(source, args)
local PETname = args[1]
local src = source
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.AddItem(PETname, 1)
TriggerClientEvent("inventory:client:ItemBox", src, QBCore.Shared.Items[PETname], "add")
end, 'admin')
QBCore.Commands.Add('addItem', 'Tilføj ting til inventar (Admin)', {}, false, function(source, item)
local Player = QBCore.Functions.GetPlayer(source)
Player.Functions.AddItem(item[1], 1)
TriggerClientEvent("inventory:client:ItemBox", source, QBCore.Shared.Items[ item[1] ], "add")
end, 'admin')
QBCore.Commands.Add('renamePet', 'Omdøb kæledyr', { { "navn", "ny navn" } }, false, function(source, args)
TriggerClientEvent("keep-companion:client:rename_name_tag", source, args[1])
end, 'admin')
-- ============================
-- Cooldown
-- ============================
-- start active cooldowns
Citizen.CreateThread(function()
local timeToClean = 600 -- sec
local count = 0
while true do
Wait(1000)
count = count + 1
local size = PlayersCooldown:onlinePlayers()
if size > 0 then
for ped, cooldown in pairs(PlayersCooldown.players) do
PlayersCooldown:updateCooldown(ped)
end
end
-- remove offline player from cooldown list
if count >= timeToClean and not count == 0 then
PlayersCooldown:cleanOflinePlayers()
count = 0
end
end
end)
RegisterNetEvent('keep-companion:server:ForceRemoveNetEntity', function(netId)
local net = NetworkGetEntityFromNetworkId(netId)
DeleteEntity(net)
end)