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

664 lines
23 KiB
Lua

local CoreName = exports['qb-core']:GetCoreObject()
-- ============================
-- Pet Class
-- ============================
ActivePed = {
data = {
-- model = '',
-- entity = '',
-- hostile = '',
-- lastCoord = '',
-- variation = '',
-- health = '',
},
onControl = -1
}
-- itemData.name is item's name
-- itemData.info.name is pet's name
--- inital pet data
function ActivePed:new(model, hostile, item, ped, netId)
-- set modelString and canHunt
local index = (#self.data + 1)
if self.data[index] == nil then
self.data[index] = {}
self.onControl = 1
else
self.onControl = self.onControl + 1
end
-- move onControll to last spawned pet
self.data[index]['model'] = model
self.data[index]['entity'] = ped
self.data[index]['netId'] = netId
self.data[index]['hostile'] = hostile
self.data[index]['itemData'] = item
self.data[index]['lastCoord'] = GetEntityCoords(ped) -- if we don't have coord we know entity is missing
self.data[index]['variation'] = item.info.variation
self.data[index]['health'] = item.info.health
for key, information in pairs(Config.pets) do
if information.name == item.name then
self.data[index]['modelString'] = information.model
self.data[index]['maxHealth'] = information.maxHealth
for w in information.distinct:gmatch("%S+") do
if w == 'yes' then
self.data[index]['canHunt'] = true
elseif w == 'no' then
self.data[index]['canHunt'] = false
end
end
return
end
end
end
--- return current active pet
function ActivePed:read()
local index = ActivePed.onControl
return ActivePed.data[index]
end
--- clean current ped data
function ActivePed:remove(index)
local netId = NetworkGetNetworkIdFromEntity(self.data[index].entity)
if not netId then return end
TriggerServerEvent('keep-companion:server:ForceRemoveNetEntity', netId)
self.data[index] = nil
-- assign onControl to valid value
if #self.data == 0 then
self.onControl = -1
return
end
for key, value in pairs(self.data) do
self.onControl = key
return
end
end
function ActivePed:removeAll()
local tmpHash = {}
for key, value in pairs(ActivePed:petsList()) do
DeletePed(value.pedHandle)
table.insert(tmpHash, value.itemData)
local currentItem = {
hash = value.itemData.info.hash or nil,
slot = value.itemData.slot or nil
}
TriggerServerEvent('keep-companion:server:updateAllowedInfo', currentItem, {
key = 'XP',
})
end
TriggerServerEvent('keep-companion:server:onPlayerUnload', tmpHash)
self.data = {}
self.onControl = -1
end
function ActivePed:switchControl(to)
if to > #self.data or to < 1 then
return
end
self.onControl = to
end
function ActivePed:findByHash(hash)
for key, data in pairs(self.data) do
if data.itemData.info.hash == hash then
return key, data
end
end
end
function ActivePed:petsList()
local tmp = {}
for key, data in pairs(self.data) do
table.insert(tmp, {
key = key,
name = data.itemData.info.name,
pedHandle = data.entity,
itemData = {
info = {
hash = data.itemData.info.hash -- used on ActivePed:removeAll()
}
}
})
end
return tmp
end
RegisterNetEvent('keep-companion:client:callCompanion')
AddEventHandler('keep-companion:client:callCompanion', function(modelName, hostileTowardPlayer, item)
-- add another layer when player spawn it inside Vehicle
local model = (tonumber(modelName) ~= nil and tonumber(modelName) or GetHashKey(modelName))
local plyPed = PlayerPedId()
local ped = nil
SetCurrentPedWeapon(plyPed, 0xA2719263, true)
ClearPedTasks(plyPed)
whistleAnimation(plyPed, 1500)
CoreName.Functions.Progressbar("callCompanion", "Kalder på kæledyr", Config.Settings.callCompanionDuration * 1000,
false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = false
}, {}, {}, {}, function()
ClearPedTasks(plyPed)
local spawnCoord = getSpawnLocation(plyPed)
ped = CreateAPed(model, spawnCoord)
local netId = NetworkGetNetworkIdFromEntity(ped)
QBCore.Functions.TriggerCallback('keep-companion:server:updatePedData', function(result)
if hostileTowardPlayer == true then
-- if player is not owner of pet it will attack player
QBCore.Functions.Notify(Lang:t('error.not_owner_of_pet'), 'error', 5000)
end
ClearPedTasks(ped)
TaskFollowTargetedPlayer(ped, plyPed, 3.0, true)
-- -- add blip to entity
if Config.Settings.PetMiniMap.showblip ~= nil and Config.Settings.PetMiniMap.showblip == true then
createBlip({
entity = ped,
sprite = Config.Settings.PetMiniMap.sprite,
colour = Config.Settings.PetMiniMap.colour,
text = item.info.name,
shortRange = false
})
end
-- init ped data inside client
ActivePed:new(modelName, hostileTowardPlayer, item, ped, netId)
local index, petData = ActivePed:findByHash(item.info.hash)
-- check for variation data
if petData.itemData.info.variation ~= nil then
PetVariation:setPedVariation(ped, modelName, petData.itemData.info.variation)
end
SetEntityMaxHealth(ped, petData.maxHealth)
SetEntityHealth(ped, math.floor(petData.itemData.info.health))
local currentHealth = GetEntityHealth(ped)
exports['qb-target']:AddTargetEntity(ped, {
options = { {
icon = "fas fa-sack-dollar",
label = "pet",
canInteract = function(entity)
return (IsEntityDead(entity) == false and ActivePed.read() ~= nil)
end,
action = function(entity)
makeEntityFaceEntity(PlayerPedId(), entity)
makeEntityFaceEntity(entity, PlayerPedId())
local playerPed = PlayerPedId()
local coords = GetEntityCoords(playerPed)
local forward = GetEntityForwardVector(playerPed)
local x, y, z = table.unpack(coords + forward * 1.0)
SetEntityCoords(entity, x, y, z, 0, 0, 0, 0)
TaskPause(entity, 5000)
Animator(entity, modelName, 'tricks', {
animation = 'petting_chop'
})
Animator(plyPed, 'A_C_Rottweiler', 'tricks', {
animation = 'petting_franklin'
})
TriggerServerEvent('hud:server:RelieveStress', Config.Balance.petting_stress_relief)
return true
end
}, {
icon = "fas fa-first-aid",
label = "Helbred",
canInteract = function(entity)
return (IsEntityDead(entity) == false and ActivePed.read() ~= nil)
end,
action = function(entity)
request_healing_process(ped, item, 'Heal')
return true
end
}, {
icon = "fas fa-first-aid",
label = "Genopliv",
canInteract = function(entity)
return (IsEntityDead(entity) == 1 and ActivePed.read() ~= nil)
end,
action = function(entity)
if not DoesEntityExist(entity) then
return false
end
request_healing_process(ped, item, 'revive')
return true
end
}, {
icon = "fa-solid fa-flask",
label = "Drik fra vandflaske",
canInteract = function(entity)
return (IsEntityDead(entity) ~= 1 and ActivePed.read() ~= nil)
end,
action = function(entity)
if not DoesEntityExist(entity) then
return false
end
start_drinking_animation()
return true
end
}
},
distance = 1.5
})
if petData.hostile == true then
TriggerServerEvent('keep-companion:server:despwan_not_owned_pet', petData.itemData.info.hash)
return
end
if currentHealth > 100 then
creatActivePetThread(ped, item)
end
end, {
item = item, model = model, entity = ped
})
end)
end)
function request_healing_process(ped, item, process_type)
local hasitem = QBCore.Functions.HasItem(Config.core_items.firstaid.item_name)
if not hasitem then QBCore.Functions.Notify(Lang:t('error.not_enough_first_aid'), 'error', 5000) return end
local plyID = PlayerPedId()
local timeout = Config.core_items.firstaid.settings.duration
local current_pet = ActivePed.data[ActivePed:findByHash(item.info.hash)]
if process_type == 'Heal' then
timeout = timeout * math.floor(Config.core_items.firstaid.settings.healing_duration_multiplier)
makeEntityFaceEntity(ped, plyID) -- pet face owner
TaskPause(ped, 5000)
else
timeout = timeout * math.floor(Config.core_items.firstaid.settings.revive_duration_multiplier)
end
makeEntityFaceEntity(plyID, ped) -- owner face pet
Animator(plyID, "PLAYER", 'revive', {
animation = 'tendtodead',
sequentialTimings = {
[1] = timeout,
[2] = 0,
[3] = 0,
step = 1,
Timeout = timeout
}
})
-- firstaidforpet
CoreName.Functions.Progressbar("reviveing", "Genopliver",
timeout * 1000, false, false, {
disableMovement = true,
disableCarMovement = true,
disableMouse = false,
disableCombat = true
}, {}, {}, {}, function()
TriggerServerEvent('keep-companion:server:revivePet', current_pet, process_type)
TaskFollowTargetedPlayer(ped, plyID, false)
end)
end
RegisterNetEvent('keep-companion:client:update_health_value', function(item, amount)
SetEntityHealth(item.entity, math.floor(amount))
end)
--- when the player is AFK for a certain time pet will wander around
---@param timeOut table
---@param afk number
local function afkWandering(timeOut, afk, plyPed, ped)
local coord = GetEntityCoords(plyPed)
if IsPedStopped(plyPed) and IsPedInAnyVehicle(plyPed) == false then
if timeOut[1] < afk.afkTimerRestAfter then
timeOut[1] = timeOut[1] + 1
-- code here
if timeOut[1] == afk.wanderingInterval then
if timeOut.lastAction == nil or (timeOut.lastAction ~= nil and timeOut.lastAction == 'animation') then
ClearPedTasks(ped) -- clear last animation
TaskWanderInArea(ped, coord, 4.0, 2, 8.0)
timeOut.lastAction = 'wandering'
end
end
if timeOut[1] == afk.animationInterval then
ClearPedTasks(ped) -- clear TaskWanderInArea
Animator(ped, ActivePed:read().model, 'siting')
timeOut.lastAction = 'animation'
end
else
timeOut[1] = 0 --
end
else
timeOut[1] = 0
end
end
--- this set of Functions will executed evetry sec to tracker pet's behaviour.
---@param ped any
function creatActivePetThread(ped, item)
local afk = Config.Balance.afk
local count = Config.DataUpdateInterval -- this value is
local plyPed = PlayerPedId()
CreateThread(function()
local tmpcount = 0
local savedData = ActivePed.data[ActivePed:findByHash(item.info.hash)]
local fninished = false
-- it's table just to have passed by reference.
local timeOut = {
0,
lastAction = nil
}
while DoesEntityExist(ped) and fninished == false do
afkWandering(timeOut, afk, plyPed, ped)
-- update every 10 sec
if tmpcount >= count then
local activeped = savedData
local currentItem = {
hash = activeped.itemData.info.hash,
slot = activeped.itemData.slot
}
TriggerServerEvent('keep-companion:server:updateAllowedInfo', currentItem, {
key = 'XP',
})
tmpcount = 0
end
tmpcount = tmpcount + 1
-- update health
local currentHealth = GetEntityHealth(savedData.entity)
if IsPedDeadOrDying(savedData.entity) == false and savedData.maxHealth ~= currentHealth and
savedData.health ~=
currentHealth then
-- ped is still alive
TriggerServerEvent('keep-companion:server:updateAllowedInfo', {
hash = savedData.itemData.info.hash,
slot = savedData.itemData.slot
}, {
key = 'health',
netId = NetworkGetNetworkIdFromEntity(ped),
})
savedData.health = currentHealth
end
-- pet is died
if IsPedDeadOrDying(savedData.entity) == 1 then
local c_health = GetEntityHealth(savedData.entity)
if c_health <= 100 then
TriggerServerEvent('keep-companion:server:updateAllowedInfo', {
hash = savedData.itemData.info.hash,
slot = savedData.itemData.slot
}, {
key = 'health',
netId = NetworkGetNetworkIdFromEntity(ped),
})
fninished = true
end
end
Wait(1000)
end
end)
end
RegisterNetEvent('keep-companion:client:forceKill', function(hash, reason)
local index, petData = ActivePed:findByHash(hash)
local c_health = GetEntityHealth(petData.entity)
if c_health < 100 then
return
end
petData.health = 0
SetEntityHealth(petData.entity, 0)
local msg = Lang:t('error.your_pet_died_by')
msg = string.format(msg, reason)
QBCore.Functions.Notify(msg, 'error', 5000)
end)
RegisterNetEvent('keep-companion:client:despawn')
AddEventHandler('keep-companion:client:despawn', function(item, revive)
if revive ~= nil and revive == true then
-- revive skip animation
local index, pedData = ActivePed:findByHash(item.info.hash)
ActivePed:remove(index)
TriggerServerEvent('keep-companion:server:setAsDespawned', item)
return
end
local plyPed = PlayerPedId()
SetCurrentPedWeapon(plyPed, 0xA2719263, true)
ClearPedTasks(plyPed)
whistleAnimation(plyPed, 1500)
CoreName.Functions.Progressbar("despawn", "Despawner", Config.Settings.despawnDuration * 1000, false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = false
}, {}, {}, {}, function()
ClearPedTasks(plyPed)
Citizen.CreateThread(function()
local index, pedData = ActivePed:findByHash(item.info.hash)
ActivePed:remove(index)
TriggerServerEvent('keep-companion:server:setAsDespawned', item)
end)
end)
end)
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
ActivePed:removeAll()
PlayerData = {} -- empty playerData
end)
-- =========================================
-- Commands Client Events
-- =========================================
RegisterNetEvent('keep-companion:client:start_feeding_animation', function()
local current_pet = ActivePed:read()
if current_pet == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
local c_health = GetEntityHealth(current_pet.entity)
if c_health <= 100.0 or current_pet.itemData.info.health <= 100.0 then
QBCore.Functions.Notify(Lang:t('error.your_pet_is_dead'), 'error', 5000)
return
end
CoreName.Functions.Progressbar("feeding", "Fodrer", Config.core_items.food.settings.duration * 1000, false, false,
{
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = false
}, {}, {}, {}, function()
TriggerServerEvent('keep-companion:server:increaseFood', current_pet.itemData)
end)
end)
RegisterNetEvent('keep-companion:client:', function()
end)
function start_drinking_animation()
local current_pet = ActivePed:read()
if current_pet == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
local c_health = GetEntityHealth(current_pet.entity)
if c_health <= 100.0 or current_pet.itemData.info.health <= 100.0 then
QBCore.Functions.Notify(Lang:t('error.your_pet_is_dead'), 'error', 5000)
return
end
CoreName.Functions.Progressbar("pet_drinking", "Drikker", Config.core_items.waterbottle.settings.duration * 1000,
false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = false
}, {}, {}, {}, function()
QBCore.Functions.TriggerCallback('keep-companion:server:decrease_thirst', function(result)
end, current_pet.itemData)
end)
end
RegisterNetEvent('keep-companion:client:filling_animation', function(item)
CoreName.Functions.Progressbar("filling_animation", "Fylder flaske",
Config.core_items.waterbottle.settings.duration * 1000, false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = false
}, {}, {}, {}, function()
TriggerServerEvent('keep-companion:server:filling_event', item)
end)
end)
RegisterNetEvent('keep-companion:client:rename_name_tag', function(item)
if ActivePed:read() == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
local name = exports['qb-input']:ShowInput({
header = "Skift navn på: " .. ActivePed:read().itemData.info.name,
submitText = "Skift navn",
inputs = { {
type = 'text',
isRequired = true,
name = 'pet_name',
text = "Skriv nyt navn"
} }
})
if name then
if not name.pet_name then
return
end
TriggerServerEvent('keep-companion:server:rename_name_tag', name.pet_name)
end
end)
RegisterNetEvent('keep-companion:client:rename_name_tagAction', function(name)
-- process of updating pet's name
local activePed = ActivePed:read() or nil
local validation = ValidatePetName(name, 12)
if activePed == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
if activePed.itemData.info.hash == nil or type(name) ~= "string" then
QBCore.Functions.Notify(Lang:t('error.failed_to_start_procces'), 'error', 5000)
return
end
if type(validation) == "table" and next(validation) ~= nil then
QBCore.Functions.Notify(Lang:t('error.failed_to_validate_name'), 'error', 5000)
if validation.reason == 'badword' then
QBCore.Functions.Notify(Lang:t('error.badword_inside_pet_name'), 'error', 5000)
print_table(validation.words)
return
elseif validation.reason == 'maxCharacter' then
QBCore.Functions.Notify(Lang:t('error.more_than_one_word_as_name'), 'error', 5000)
return
end
return
end
CoreName.Functions.Progressbar("waitingForName", "Venter på navn",
Config.core_items.nametag.settings.duration * 1000, false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = true
}, {}, {}, {}, function()
QBCore.Functions.TriggerCallback('keep-companion:server:renamePet', function(result)
if type(result) == "string" then
QBCore.Functions.Notify(Lang:t('success.pet_rename_was_successful') .. result, 'success', 5000)
end
end, {
hash = activePed.itemData.info.hash or nil,
slot = activePed.itemData.slot or nil,
name = name
})
end)
end)
RegisterNetEvent('keep-companion:client:collar_process', function()
-- process of updating pet's owernship
local activePed = ActivePed:read() or nil
if activePed == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
if activePed.itemData.info.hash == nil then
QBCore.Functions.Notify(Lang:t('error.failed_to_find_pet'), 'error', 5000)
return
end
local inputData = exports['qb-input']:ShowInput({
header = "Ny ejer ID: ",
submitText = "Bekræft",
inputs = {
{
type = 'number',
isRequired = true,
name = 'cid',
text = "Ny ejer ID"
},
}
})
if inputData then
if not inputData.cid then
return
end
CoreName.Functions.Progressbar("waitingForOwenership", "Venter på den nye ejer",
Config.core_items.collar.settings.duration * 1000, false, false, {
disableMovement = false,
disableCarMovement = false,
disableMouse = false,
disableCombat = true
}, {}, {}, {},
function()
local c_pet = ActivePed:read()
if c_pet == nil then
QBCore.Functions.Notify(Lang:t('error.no_pet_under_control'), 'error', 5000)
return
end
QBCore.Functions.TriggerCallback('keep-companion:server:collar_change_owenership', function(result)
if result.state == false then
QBCore.Functions.Notify(result.msg, 'error', 5000)
return
end
QBCore.Functions.Notify(result.msg, 'success', 5000)
end, {
new_owner_cid = inputData.cid,
hash = ActivePed:read().itemData.info.hash,
})
end
)
end
end)