local QBCore = exports['qb-core']:GetCoreObject() Update = {} --- get random pet name ---@param type 'species' ---@param gender integer function NameGenerator(type, gender) local names = { dog = { { "Max", "Buddy", "Charlie", "Jack", "Cooper", "Rocky", "Toby", "Tucker", "Jake", "Bear", "Duke", "Teddy", "Oliver", "Riley", "Bailey", "Bentley", "Milo", "Buster", "Cody", "Dexter", "Winston", "Murphy", "Leo", "Lucky", "Oscar", "Louie", "Zeus", "Henry", "Sam", "Harley", "Baxter", "Gus", "Sammy", "Jackson", "Bruno", "Diesel", "Jax", "Gizmo", "Bandit", "Rusty", "Marley", "Jasper", "Brody", "Roscoe", "Hank", "Otis", "Bo", "Joey", "Beau", "Ollie", "Tank", "Shadow", "Peanut", "Hunter", "Scout", "Blue", "Rocco", "Simba", "Tyson", "Ziggy", "Boomer", "Romeo", "Apollo", "Ace", "Luke", "Rex", "Finn", "Chance", "Rudy", "Loki", "Moose", "George", "Samson", "Coco", "Benny", "Thor", "Rufus", "Prince", "Chester", "Brutus", "Scooter", "Chico", "Spike", "Gunner", "Sparky", "Mickey", "Kobe", "Chase", "Oreo", "Frankie", "Mac", "Benji", "Bubba", "Champ", "Brady", "Elvis", "Copper", "Cash", "Archie", "Walter" }, { "Bella", "Lucy", "Daisy", "Molly", "Lola", "Sophie", "Sadie", "Maggie", "Chloe", "Bailey", "Roxy", "Zoey", "Lily", "Luna", "Coco", "Stella", "Gracie", "Abby", "Penny", "Zoe", "Ginger", "Ruby", "Rosie", "Lilly", "Ellie", "Mia", "Sasha", "Lulu", "Pepper", "Nala", "Lexi", "Lady", "Emma", "Riley", "Dixie", "Annie", "Maddie", "Piper", "Princess", "Izzy", "Maya", "Olive", "Cookie", "Roxie", "Angel", "Belle", "Layla", "Missy", "Cali", "Honey", "Millie", "Harley", "Marley", "Holly", "Kona", "Shelby", "Jasmine", "Ella", "Charlie", "Minnie", "Willow", "Phoebe", "Callie", "Scout", "Katie", "Dakota", "Sugar", "Sandy", "Josie", "Macy", "Trixie", "Winnie", "Peanut", "Mimi", "Hazel", "Mocha", "Cleo", "Hannah", "Athena", "Lacey", "Sassy", "Lucky", "Bonnie", "Allie", "Brandy", "Sydney", "Casey", "Gigi", "Baby", "Madison", "Heidi", "Sally", "Shadow", "Cocoa", "Pebbles", "Misty", "Nikki", "Lexie", "Grace", "Sierra" } } } local size = #names[type][gender] return names[type][gender][math.random(1, size)] end local function initInfoHelper(Player, slot, data) if Player.PlayerData.items[slot] then Player.PlayerData.items[slot].info = data end Player.Functions.SetInventory(Player.PlayerData.items, true) end --- inital pet data after player bought pet ---@param source any ---@param item any function initItem(source, item) local src = source local Player = QBCore.Functions.GetPlayer(src) local pet_information = find_pet_model_by_item_name(item.name) local random = math.random(1, 2) local gender = { true, false } local maxHealth = 200 item.info = {} item.info.hash = tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4)) item.info.name = NameGenerator('dog', random) item.info.gender = gender[random] item.info.age = 0 item.info.food = 100 item.info.thirst = 0 item.info.owner = Player.PlayerData.charinfo item.info.level = 0 item.info.XP = 0 item.info.health = pet_information.maxHealth or maxHealth -- inital variation item.info.variation = PetVariation:getRandomPedVariationsName(pet_information.model, true) initInfoHelper(Player, item.slot, item.info) -- do extras step if we want to cutomize pets if Config.Settings.let_players_cutomize_their_pet_after_purchase then local information = { pet_variation_list = PetVariation:getPedVariationsNameList(pet_information.model), pet_information = pet_information, disable = { rename = false }, type = 'init' } TriggerClientEvent('keep-companion:client:initialization_process', src, item, information) end end function find_pet_model_by_item_name(item_name) for k, v in pairs(Config.pets) do if v.name == item_name then return v end end end RegisterNetEvent('keep-companion:server:compelete_initialization_process', function(item, process_type) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end TriggerEvent('keep-companion:server:keep-companion:server:compelete_initialization_process_last_step', source, item, Player, process_type) if process_type == 'init' then return end Player.Functions.RemoveItem(Config.core_items.groomingkit.item_name, 1) end) RegisterNetEvent('keep-companion:server:keep-companion:server:compelete_initialization_process_last_step', function(src, item, Player, process_type) local pet_information = find_pet_model_by_item_name(item.name) if not pet_information then return end local items = Player.Functions.GetItemsByName(item.name) if not items then return end if process_type == Config.core_items.groomingkit.item_name then local petData = Pet:findbyhash(src, item.info.hash) if Player.PlayerData.charinfo.phone ~= petData.info.owner.phone then TriggerClientEvent('QBCore:Notify', src, Lang:t('error.not_owner_of_pet'), 'error', 2500) return end -- force data that we don't want to get by client side item.info.age = petData.info.age item.info.food = petData.info.food item.info.thirst = petData.info.thirst -- check owner item.info.owner = Player.PlayerData.charinfo item.info.level = petData.info.level item.info.XP = petData.info.XP item.info.health = petData.info.health else -- force data that we don't want to get by client side item.info.age = 0 item.info.food = 100 item.info.thirst = 0 item.info.owner = Player.PlayerData.charinfo item.info.level = 0 item.info.XP = 0 item.info.health = pet_information.maxHealth end local sever_item = nil for key, value in pairs(items) do if value.info.hash == item.info.hash then sever_item = value break end end if not sever_item then return end initInfoHelper(Player, sever_item.slot, item.info) if process_type == Config.core_items.groomingkit.item_name then TriggerClientEvent('QBCore:Notify', src, Lang:t('success.successful_grooming'), 'success', 2500) Pet:despawnPet(src, item, true) -- despawn dead pet end end) -- 1 --> 7 year old CalorieCalData = { dog = { maximumCal = 2000, activity = { low = 1.6, high = 5.0 }, RER = function(lbs) return 70 * (lbs ^ (0.75)) end }, cat = { maximumCal = 1000, activity = { low = 1.2, high = 1.8 }, RER = function(lbs) return 40 * (lbs ^ (0.75)) end } } function CalorieCalData:calRER(lbs, type) local res = 0 res = math.floor(self[type]['RER'](lbs)) return res end function CalorieCalData:convertWeightToLbs(weight) return (weight * 10) / 500 end local function convert_xp_to_level(xp) -- hardcoded level 0 if xp >= 0 and xp <= 75 then return 0 end local maxExp = 0 local minExp = 0 for i = 1, 51, 1 do maxExp = math.floor(math.floor((i + 300) * (2 ^ (i / 7))) / 4) minExp = math.floor(math.floor(((i - 1) + 300) * (2 ^ ((i - 1) / 7))) / 4) if xp >= minExp and xp <= maxExp then return i end end end local function calculate_next_xp_value(level) local maxExp = math.floor(math.floor((level + 300) * (2 ^ (level / 7))) / 4) local minExp = math.floor(math.floor(((level - 1) + 300) * (2 ^ ((level - 1) / 7))) / 4) local dif = maxExp - minExp local pr = math.floor(maxExp / minExp) local multi = 1 return math.floor(dif / (multi * (level + 1) * pr)) end local function current_level_max_xp(level) return math.floor(math.floor((level + 300) * (2 ^ (level / 7))) / 4) end function Update:xp(source, current_pet_data) local level = convert_xp_to_level(math.floor(current_pet_data.info.XP)) local pet_name = current_pet_data.info.name if level > Config.Balance.maximumLevel then -- pet reached maximumLevel return end if current_pet_data.info.XP == 0 then current_pet_data.info.XP = 75 end current_pet_data.info.XP = current_pet_data.info.XP + calculate_next_xp_value(level) -- increase level when pet reached max exp of current level if current_pet_data.info.XP > current_level_max_xp(level) then current_pet_data.info.level = level + 1 local msg = string.format(Lang:t('info.level_up'), pet_name, current_pet_data.info.level) TriggerClientEvent('QBCore:Notify', source, msg) end end function Update:health(source, data, current_pet_data) local pet_name = current_pet_data.info.name local net_pet = NetworkGetEntityFromNetworkId(data.netId) if net_pet == 0 then return end local c_health = GetEntityHealth(net_pet) if current_pet_data.info.health == c_health then return end if c_health <= 100 then local msg = string.format(Lang:t('error.pet_died'), pet_name) TriggerClientEvent('QBCore:Notify', source, msg, 'error') c_health = 0 end current_pet_data.info.health = c_health Pet:save_all_info(source, current_pet_data.info.hash) -- saving health should be outside loop to prevent some expolits end function Update:food(petData, process_type) if petData == nil or process_type == nil then return end if petData.info.food == 0 then if petData.info.health == 0 or petData.info.health <= 100 then -- force kill pet petData.info.health = 0 -- rewrite it just in case value changed for some reason return end petData.info.health = petData.info.health - 0.2 return end if petData.info.food > 0 then petData.info.food = petData.info.food - 1 -- make sure food value not negative if petData.info.food < 0 then petData.info.food = 0 end return end end local thirst_value_increase_per_tick = Config.core_items.waterbottle.settings.thirst_value_increase_per_tick function Update:thirst(petData, process_type) if petData == nil or process_type == nil then return end if petData.info.thirst == nil then petData.info.thirst = 0.0 end if petData.info.thirst >= 100.0 then if petData.info.health == 0 or petData.info.health <= 100 then petData.info.health = 0 petData.info.thirst = 100 return end petData.info.health = petData.info.health - 0.5 return end if petData.info.thirst <= 100 then petData.info.thirst = petData.info.thirst + thirst_value_increase_per_tick -- make sure thirst value not negative if petData.info.thirst < 0 then petData.info.thirst = 0 end return end end QBCore.Functions.CreateCallback('keep-companion:server:collar_change_owenership', function(source, cb, data) if type(data.new_owner_cid) == "string" then data.new_owner_cid = tonumber(data.new_owner_cid) end local player_owner = QBCore.Functions.GetPlayer(source) if player_owner == nil then return end local player_new_owner = QBCore.Functions.GetPlayer(data.new_owner_cid) if data.new_owner_cid == source then cb({ state = false, msg = Lang:t('error.failed_to_transfer_ownership_same_owner') }) return end if player_new_owner == nil or next(data) == nil then cb({ state = false, msg = Lang:t('error.failed_to_transfer_ownership_could_not_find_new_owner_id') }) return end local hash = data.hash local current_pet_data = Pet:findbyhash(source, hash) if type(current_pet_data.info.owner) ~= "table" or next(current_pet_data.info.owner) == nil then cb({ state = false, msg = Lang:t('error.failed_to_transfer_ownership_missing_current_owner') }) return end if player_owner.Functions.RemoveItem('collarpet', 1) ~= true then TriggerClientEvent('QBCore:Notify', source, Lang:t('error.failed_to_remove_item_from_inventory'), 'error', 2500) return end current_pet_data.info.owner = player_new_owner.PlayerData.charinfo Pet:save_all_info(source, hash) Pet:despawnPet(source, { info = { hash = hash } }, true) cb({ state = true, msg = Lang:t('success.successful_ownership_transfer') }) end) -- ============================ -- Cooldown -- ============================ local usageCooldown = Config.Settings.itemUsageCooldown * 1000 PlayersCooldown = { players = {} } function PlayersCooldown:initCooldown(player) self.players[player] = usageCooldown end function PlayersCooldown:cleanOflinePlayers() local onlinePlayers = QBCore.Functions.GetPlayers() for ID, cooldown in pairs(self.players) do for key, id in pairs(onlinePlayers) do if ID == id then goto here end end self.players[ID] = nil ::here:: end end function PlayersCooldown:updateCooldown(player) if self.players[player] > 0 then self.players[player] = self.players[player] - 1000 end return 0 end function PlayersCooldown:isOnCooldown(player) if self.players[player] == nil then PlayersCooldown:initCooldown(player) return 0 elseif self.players[player] == 0 then PlayersCooldown:initCooldown(player) return 0 elseif self.players[player] > 0 then return self.players[player] end end function PlayersCooldown:onlinePlayers() local count = 0 for _ in pairs(self.players) do count = count + 1 end return count end