411 lines
14 KiB
Lua
411 lines
14 KiB
Lua
|
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
|