Part 18
This commit is contained in:
parent
5d95c0182e
commit
7359d3963b
22
resources/[ps]/ps-adminmenu/ui/src/store/actions.ts
Normal file
22
resources/[ps]/ps-adminmenu/ui/src/store/actions.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const ALL_ACTIONS = writable<boolean>(true);
|
||||
|
||||
export const ACTION = writable<any[]>(null);
|
||||
|
||||
interface DROPDOWN_DATA {
|
||||
label: string;
|
||||
type: string;
|
||||
option?: string;
|
||||
data?: any[];
|
||||
event?: string;
|
||||
}
|
||||
|
||||
interface ACITONS_DATA {
|
||||
id: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
event?: string;
|
||||
perms: string[];
|
||||
dropdown?: DROPDOWN_DATA[];
|
||||
}
|
8
resources/[ps]/ps-adminmenu/ui/src/store/data.ts
Normal file
8
resources/[ps]/ps-adminmenu/ui/src/store/data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const VEHICLE_DATA = writable<any[]>(null);
|
||||
export const ITEM_DATA = writable<any[]>(null);
|
||||
export const JOB_DATA = writable<any[]>(null);
|
||||
export const GANG_DATA = writable<any[]>(null);
|
||||
export const LOCATION_DATA = writable<any[]>(null);
|
||||
export const PED_LIST = writable<any[]>(null);
|
9
resources/[ps]/ps-adminmenu/ui/src/store/entityInfo.ts
Normal file
9
resources/[ps]/ps-adminmenu/ui/src/store/entityInfo.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const ENTITY_INFO = writable<ENTITY_INFO[]>(null);
|
||||
|
||||
interface ENTITY_INFO {
|
||||
show: boolean;
|
||||
name: string;
|
||||
hash: string;
|
||||
}
|
32
resources/[ps]/ps-adminmenu/ui/src/store/players.ts
Normal file
32
resources/[ps]/ps-adminmenu/ui/src/store/players.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const PLAYER_DATA = writable(null);
|
||||
|
||||
export const SELECTED_PLAYER = writable(null);
|
||||
|
||||
export const PLAYER = writable<any[]>(null);
|
||||
|
||||
export const PLAYER_VEHICLES = writable<PLAYER_VEHICLES[]>(null);
|
||||
|
||||
interface PLAYER_DATA {
|
||||
id?: string;
|
||||
name?: string;
|
||||
cid?: string;
|
||||
license?: string;
|
||||
discord?: string;
|
||||
steam?: string;
|
||||
fivem?: string;
|
||||
vehicles?: PLAYER_VEHICLES[];
|
||||
}
|
||||
|
||||
interface PLAYER_VEHICLES {
|
||||
id?: string;
|
||||
cid?: string;
|
||||
label?: string;
|
||||
brand?: string;
|
||||
model?: string;
|
||||
plate?: string;
|
||||
fuel?: string;
|
||||
engine?: string;
|
||||
body?: string;
|
||||
}
|
11
resources/[ps]/ps-adminmenu/ui/src/store/reports.ts
Normal file
11
resources/[ps]/ps-adminmenu/ui/src/store/reports.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const REPORT = writable<REPORT_DATA[]>(null);
|
||||
export const REPORTS = writable<REPORT_DATA>(null);
|
||||
|
||||
interface REPORT_DATA {
|
||||
message: string,
|
||||
citizenid: string,
|
||||
fullname: string,
|
||||
time: number,
|
||||
}
|
29
resources/[ps]/ps-adminmenu/ui/src/store/server.ts
Normal file
29
resources/[ps]/ps-adminmenu/ui/src/store/server.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const RESOURCE = writable<RESOURCE_DATA[]>(null);
|
||||
export const RESOURCES = writable<RESOURCE_DATA>(null);
|
||||
export const COMMANDS = writable<COMMANDS_DATA[]>(null);
|
||||
|
||||
export const SERVER = writable<SERVER_DATA[]>(null);
|
||||
|
||||
interface RESOURCE_DATA {
|
||||
name?: string;
|
||||
author?: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
resourceState?: string;
|
||||
}
|
||||
|
||||
interface SERVER_DATA {
|
||||
TotalCash?: string;
|
||||
TotalBank?: string;
|
||||
TotalItems?: string;
|
||||
CharacterCount?: string;
|
||||
VehicleCount?: string;
|
||||
BansCount?: string;
|
||||
UniquePlayers?: string;
|
||||
}
|
||||
|
||||
interface COMMANDS_DATA {
|
||||
name?: string;
|
||||
}
|
11
resources/[ps]/ps-adminmenu/ui/src/store/staffchat.ts
Normal file
11
resources/[ps]/ps-adminmenu/ui/src/store/staffchat.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
interface ChatData {
|
||||
message: string
|
||||
citizenid: string
|
||||
fullname: string
|
||||
time: number
|
||||
}
|
||||
|
||||
export const Message = writable<ChatData[]>(null)
|
||||
export const Messages = writable<ChatData>(null)
|
12
resources/[ps]/ps-adminmenu/ui/src/store/stores.ts
Normal file
12
resources/[ps]/ps-adminmenu/ui/src/store/stores.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const VISIBILITY = writable<boolean>(false);
|
||||
export const BROWSER_MODE = writable<boolean>(false);
|
||||
export const RESOURCE_NAME = writable<string>("");
|
||||
|
||||
export const DEV_MODE = writable<boolean>(false);
|
||||
|
||||
export const MENU_WIDE = writable<boolean>(false);
|
||||
export const ACTIVE_PAGE = writable<string>("Actions");
|
||||
|
||||
export const searchActions = writable('');
|
11
resources/[ps]/ps-adminmenu/ui/src/store/togglecoords.ts
Normal file
11
resources/[ps]/ps-adminmenu/ui/src/store/togglecoords.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const TOGGLE_COORDS = writable<COORDS_DATA[]>(null);
|
||||
|
||||
interface COORDS_DATA {
|
||||
show: boolean;
|
||||
x: string;
|
||||
y: string;
|
||||
z: string;
|
||||
heading?: string;
|
||||
}
|
15
resources/[ps]/ps-adminmenu/ui/src/store/vehicle_dev.ts
Normal file
15
resources/[ps]/ps-adminmenu/ui/src/store/vehicle_dev.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const VEHICLE_DEV = writable<VEHICLE_DATA[]>(null);
|
||||
|
||||
interface VEHICLE_DATA {
|
||||
show: boolean;
|
||||
name: string;
|
||||
model: string;
|
||||
netID: string;
|
||||
engine_health?: string;
|
||||
body_health?: string;
|
||||
plate?: string;
|
||||
fuel?: string;
|
||||
speed?: string;
|
||||
}
|
0
resources/[ps]/ps-adminmenu/ui/src/typings/type.ts
Normal file
0
resources/[ps]/ps-adminmenu/ui/src/typings/type.ts
Normal file
32
resources/[ps]/ps-adminmenu/ui/src/utils/ReceiveNUI.ts
Normal file
32
resources/[ps]/ps-adminmenu/ui/src/utils/ReceiveNUI.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
|
||||
interface NuiMessage<T = unknown> {
|
||||
action: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that manage events listeners for receiving data from the client scripts
|
||||
* @param action The specific `action` that should be listened for.
|
||||
* @param handler The callback function that will handle data relayed by this function
|
||||
*
|
||||
* @example
|
||||
* useNuiEvent<{VISIBILITY: true, wasVisible: 'something'}>('setVisible', (data) => {
|
||||
* // whatever logic you want
|
||||
* })
|
||||
*
|
||||
**/
|
||||
|
||||
export function ReceiveNUI<T = unknown>(
|
||||
action: string,
|
||||
handler: (data: T) => void
|
||||
) {
|
||||
const eventListener = (event: MessageEvent<NuiMessage<T>>) => {
|
||||
const { action: eventAction, data } = event.data;
|
||||
|
||||
eventAction === action && handler(data);
|
||||
};
|
||||
onMount(() => window.addEventListener("message", eventListener));
|
||||
onDestroy(() => window.removeEventListener("message", eventListener));
|
||||
}
|
||||
|
43
resources/[ps]/ps-adminmenu/ui/src/utils/SendNUI.ts
Normal file
43
resources/[ps]/ps-adminmenu/ui/src/utils/SendNUI.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { BROWSER_MODE, RESOURCE_NAME } from '@store/stores'
|
||||
|
||||
let isBrowserMode: boolean = false;
|
||||
BROWSER_MODE.subscribe((value: boolean) => {
|
||||
isBrowserMode = value;
|
||||
});
|
||||
|
||||
let debugResName: string = "";
|
||||
RESOURCE_NAME.subscribe((value: string) => {
|
||||
debugResName = value;
|
||||
});
|
||||
|
||||
/**
|
||||
* @param eventName - The endpoint eventname to target
|
||||
* @param data - Data you wish to send in the NUI Callback
|
||||
*
|
||||
* @return returnData - A promise for the data sent back by the NuiCallbacks CB argument
|
||||
*/
|
||||
|
||||
export async function SendNUI<T = any>(
|
||||
eventName: string,
|
||||
data: unknown = {},
|
||||
debugReturn?: T
|
||||
): Promise<T> {
|
||||
if ((isBrowserMode == true && debugReturn) || (isBrowserMode == true)) {
|
||||
return Promise.resolve(debugReturn || {} as T)
|
||||
}
|
||||
const options = {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
|
||||
const resourceName = (window as any).GetParentResourceName
|
||||
? (window as any).GetParentResourceName()
|
||||
: debugResName;
|
||||
|
||||
|
||||
const resp: Response = await fetch(`https://${resourceName}/${eventName}`, options);
|
||||
return await resp.json()
|
||||
}
|
30
resources/[ps]/ps-adminmenu/ui/src/utils/debugData.ts
Normal file
30
resources/[ps]/ps-adminmenu/ui/src/utils/debugData.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {isEnvBrowser} from "./misc";
|
||||
|
||||
interface DebugEvent<T = any> {
|
||||
action: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulates dispatching an event using SendNuiMessage in the lua scripts.
|
||||
* This is used when developing in browser
|
||||
*
|
||||
* @param events - The event you want to cover
|
||||
* @param timer - How long until it should trigger (ms)
|
||||
*/
|
||||
export const debugData = <P>(events: DebugEvent<P>[], timer = 0): void => {
|
||||
if (isEnvBrowser()) {
|
||||
for (const event of events) {
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent("message", {
|
||||
data: {
|
||||
action: event.action,
|
||||
data: event.data,
|
||||
},
|
||||
})
|
||||
);
|
||||
}, timer);
|
||||
}
|
||||
}
|
||||
};
|
1
resources/[ps]/ps-adminmenu/ui/src/utils/misc.ts
Normal file
1
resources/[ps]/ps-adminmenu/ui/src/utils/misc.ts
Normal file
@ -0,0 +1 @@
|
||||
export const isEnvBrowser = (): boolean => !(window as any).invokeNative;
|
79
resources/[ps]/ps-adminmenu/ui/src/utils/timeAgo.ts
Normal file
79
resources/[ps]/ps-adminmenu/ui/src/utils/timeAgo.ts
Normal file
@ -0,0 +1,79 @@
|
||||
const MONTH_NAMES = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
]
|
||||
|
||||
function getFormattedDate(date, prefomattedDate = false, hideYear = false) {
|
||||
const day = date.getDate()
|
||||
const month = MONTH_NAMES[date.getMonth()]
|
||||
const year = date.getFullYear()
|
||||
const hours = date.getHours()
|
||||
let minutes = date.getMinutes()
|
||||
|
||||
if (minutes < 10) {
|
||||
minutes = `0${minutes}`
|
||||
}
|
||||
|
||||
if (prefomattedDate) {
|
||||
return `${prefomattedDate} at ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
if (hideYear) {
|
||||
return `${day}. ${month} at ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
return `${day}. ${month} ${year}. at ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
export function timeAgo(dateParam) {
|
||||
if (!dateParam) {
|
||||
return 'Unknown'
|
||||
}
|
||||
|
||||
let date
|
||||
try {
|
||||
date = typeof dateParam === 'object' ? dateParam : new Date(dateParam)
|
||||
} catch (e) {
|
||||
return 'Invalid date'
|
||||
}
|
||||
|
||||
if (isNaN(date)) {
|
||||
return 'Invalid date'
|
||||
}
|
||||
const DAY_IN_MS = 86400000
|
||||
const today = new Date()
|
||||
const yesterday = new Date(today - DAY_IN_MS)
|
||||
const seconds = Math.round((today - date) / 1000)
|
||||
const minutes = Math.round(seconds / 60)
|
||||
const isToday = today.toDateString() === date.toDateString()
|
||||
const isYesterday = yesterday.toDateString() === date.toDateString()
|
||||
const isThisYear = today.getFullYear() === date.getFullYear()
|
||||
|
||||
if (seconds < 5) {
|
||||
return 'Just Now'
|
||||
} else if (seconds < 60) {
|
||||
return `${seconds} Seconds ago`
|
||||
} else if (seconds < 90) {
|
||||
return 'A minute ago'
|
||||
} else if (minutes < 60) {
|
||||
return `${minutes} Minutes ago`
|
||||
} else if (isToday) {
|
||||
return getFormattedDate(date, 'Today')
|
||||
} else if (isYesterday) {
|
||||
return getFormattedDate(date, 'Yesterday')
|
||||
} else if (isThisYear) {
|
||||
return getFormattedDate(date, false, true)
|
||||
}
|
||||
|
||||
return getFormattedDate(date)
|
||||
}
|
86
resources/[ps]/ps-buffs/README.md
Normal file
86
resources/[ps]/ps-buffs/README.md
Normal file
@ -0,0 +1,86 @@
|
||||
# ps-buffs
|
||||
|
||||
### Dependencies
|
||||
***
|
||||
- PS-Hud: https://github.com/Project-Sloth/ps-hud
|
||||
### General Information
|
||||
***
|
||||
- Buffs are new effects that a player can get. Example: Stamina Buff makes a player run faster.
|
||||
- They show up as a new icon
|
||||
|
||||
- Enhancements are making statuses you already have better. Example: Health Buff heals a player periodically
|
||||
- They show up as yellow on the status they are affecting. Example: Armor buffs makes the armor icon yellow
|
||||
|
||||
- By default adding buffs to a player will just have the buff icon show
|
||||
- You need to add logic to give the player buff effects (below you can see examples)
|
||||
|
||||
- Avaliable buffs you can pick from are in the config file (shared/config.lua)
|
||||
|
||||
### Add a buff to a player
|
||||
```lua
|
||||
-- Function signature - buffName: string, time: int (1 second = 1000)
|
||||
exports['ps-buffs']:AddBuff(buffName, time)
|
||||
|
||||
-- Example -- Adds a hacking buff for 15 seconds, which the player would see a hacking buff icon on their screen
|
||||
exports['ps-buffs']:AddBuff("hacking", 15000)
|
||||
```
|
||||
|
||||
### Check if player has a buff
|
||||
```lua
|
||||
-- Function signature - buffName: string
|
||||
exports['ps-buffs']:HasBuff(buffName)
|
||||
|
||||
-- Example -- Check if a player has the hacking buff and make it easier to hack something
|
||||
if exports['ps-buffs']:HasBuff("hacking") then
|
||||
-- give player more time or less complicated puzzle
|
||||
end
|
||||
```
|
||||
|
||||
### Buff Effects
|
||||
***
|
||||
|
||||
- We currently have the following buff effects implemented that you can call:
|
||||
- Stamina Buff - Makes a player run faster and generate a random partial amount of stamina
|
||||
```lua
|
||||
-- Function signature - time: int (1 second = 1000), value: double (float)
|
||||
exports['ps-buffs']:StaminaBuffEffect(time, value)
|
||||
|
||||
-- Example -- Adds a stamina buff for 15 seconds and a player runs 1.4 faster.
|
||||
exports['ps-buffs']:StaminaBuffEffect(15000, 1.4)
|
||||
```
|
||||
- Swimming Buff - Makes a player swim faster and generate a random partial amount of stamina
|
||||
```lua
|
||||
-- Function signature - time: int (1 second = 1000), value: double (float)
|
||||
exports['ps-buffs']:SwimmingBuffEffect(time, value)
|
||||
|
||||
-- Example -- Adds a swimming buff for 20 seconds and a player swims 1.4 faster.
|
||||
exports['ps-buffs']:SwimmingBuffEffect(20000, 1.4)
|
||||
```
|
||||
- Health Buff - Makes a player's health partially regenerate periodically
|
||||
```lua
|
||||
-- Function signature - time: int (1 second = 1000), value: int
|
||||
exports['ps-buffs']:AddHealthBuff(time, value)
|
||||
|
||||
-- Example -- Adds a health buff for 10 seconds and a player periodically gains 10 health.
|
||||
exports['ps-buffs']:AddHealthBuff(10000, 10)
|
||||
```
|
||||
- Armor Buff - Makes a player's armor partially regenerate periodically
|
||||
```lua
|
||||
-- Function signature - time: int (1 second = 1000), value: int
|
||||
exports['ps-buffs']:AddArmorBuff(time, value)
|
||||
|
||||
-- Example -- Adds a armor buff for 30 seconds and a player periodically gains 10 armor.
|
||||
exports['ps-buffs']:AddArmorBuff(30000, 10)
|
||||
```
|
||||
- Stress Buff - Makes a player's stress partially decrease periodically
|
||||
```lua
|
||||
-- Function signature - time: int (1 second = 1000), value: int
|
||||
exports['ps-buffs']:AddStressBuff(time, value)
|
||||
|
||||
-- Example -- Removes stress for 30 seconds and removes 10 units every 5 seconds
|
||||
exports['ps-buffs']:AddStressBuff(30000, 10)
|
||||
```
|
||||
|
||||
## Credits
|
||||
- The majority of the lua code comes from [qb-enhancements](https://github.com/IdrisDose/qb-enhancements) by [IdrisDose](https://github.com/IdrisDose)
|
||||
- Credits to my boys Silent, Snipe and fjamzoo for help with getting things in place for this to all be possible
|
217
resources/[ps]/ps-buffs/client/main.lua
Normal file
217
resources/[ps]/ps-buffs/client/main.lua
Normal file
@ -0,0 +1,217 @@
|
||||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
|
||||
local function GetBuffs()
|
||||
-- If making multiple calls to GetBuffs we dont get a result of the 2nd call
|
||||
-- The wait seems to fix the issue
|
||||
Wait(500)
|
||||
local p = promise.new()
|
||||
QBCore.Functions.TriggerCallback('buffs:server:fetchBuffs', function(result)
|
||||
p:resolve(result)
|
||||
end)
|
||||
return Citizen.Await(p)
|
||||
end
|
||||
|
||||
--- Method to fetch if player has buff with name and is not nil
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @return bool
|
||||
local function HasBuff(buffName)
|
||||
local buffs = GetBuffs()
|
||||
if buffs then
|
||||
return buffs[buffName] ~= nil
|
||||
end
|
||||
return false
|
||||
end exports('HasBuff', HasBuff)
|
||||
|
||||
--- Method to fetch buff details if player has buff active
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @return table
|
||||
local function GetBuff(buffName)
|
||||
local buffData = Config.Buffs[buffName]
|
||||
-- Check if we were given a correct buff name
|
||||
if buffData == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local buffs = GetBuffs()
|
||||
local time = nil
|
||||
|
||||
if buffs then
|
||||
time = buffs[buffName]
|
||||
end
|
||||
|
||||
if time == nil then
|
||||
time = 0
|
||||
end
|
||||
|
||||
if buffData.type == 'buff' then
|
||||
return {
|
||||
time = time,
|
||||
buffName = buffName,
|
||||
iconName = buffData.iconName,
|
||||
iconColor = buffData.iconColor,
|
||||
progressColor = buffData.progressColor,
|
||||
progressValue = (time * 100) / buffData.maxTime,
|
||||
type = buffData.type,
|
||||
}
|
||||
else
|
||||
return {
|
||||
time = time,
|
||||
iconColor = buffData.iconColor,
|
||||
maxTime = buffData.maxTime,
|
||||
type = buffData.type,
|
||||
}
|
||||
end
|
||||
end exports('GetBuff', GetBuff)
|
||||
|
||||
--- Method to fetch nui details of all buffs, used when a player that had buffs
|
||||
--- logged out and back in to the server
|
||||
--- @return table | nil
|
||||
local function GetBuffNUIData()
|
||||
local buffs = GetBuffs()
|
||||
|
||||
if buffs == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local nuiData = {}
|
||||
|
||||
for buffName, buffTime in pairs(buffs) do
|
||||
local buffData = Config.Buffs[buffName]
|
||||
|
||||
if buffData.type == 'buff' then
|
||||
nuiData[buffName] = {
|
||||
buffName = buffName,
|
||||
display = true,
|
||||
iconName = buffData.iconName,
|
||||
iconColor = buffData.iconColor,
|
||||
progressColor = buffData.progressColor,
|
||||
progressValue = (buffTime * 100) / buffData.maxTime,
|
||||
}
|
||||
else
|
||||
nuiData[buffName] = {
|
||||
display = true,
|
||||
enhancementName = buffName,
|
||||
iconColor = buffData.iconColor
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return nuiData
|
||||
end exports('GetBuffNUIData', GetBuffNUIData)
|
||||
|
||||
--- Method to add buff to player
|
||||
--- @param playerID string - Player identifier
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @return bool - Success of removing the player buff
|
||||
local function AddBuff(buffName, time)
|
||||
local p = promise.new()
|
||||
QBCore.Functions.TriggerCallback('buffs:server:addBuff', function(result)
|
||||
p:resolve(result)
|
||||
end, buffName, time)
|
||||
return Citizen.Await(p)
|
||||
end exports('AddBuff', AddBuff)
|
||||
|
||||
|
||||
|
||||
|
||||
--- Custom Buffs, edit to your liking ---
|
||||
|
||||
--- Method to add stamina buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of speed boost the player will recieve
|
||||
local hasStaminaBuffActive = false
|
||||
local function StaminaBuffEffect(time, value)
|
||||
AddBuff("stamina", time)
|
||||
if not hasStaminaBuffActive then
|
||||
hasStaminaBuffActive = true
|
||||
CreateThread(function()
|
||||
SetRunSprintMultiplierForPlayer(PlayerId(), value)
|
||||
while exports['ps-buffs']:HasBuff("stamina") do
|
||||
Wait(500)
|
||||
SetPlayerStamina(PlayerId(), GetPlayerStamina(PlayerId()) + math.random(1,10))
|
||||
end
|
||||
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
|
||||
hasStaminaBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('StaminaBuffEffect', StaminaBuffEffect)
|
||||
|
||||
--- Method to add swimming buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of swimming speed boost the player will recieve
|
||||
local hasSwimmingBuffActive = false
|
||||
local function SwimmingBuffEffect(time, value)
|
||||
AddBuff("swimming", time)
|
||||
if not hasSwimmingBuffActive then
|
||||
hasSwimmingBuffActive = true
|
||||
CreateThread(function()
|
||||
SetSwimMultiplierForPlayer(PlayerId(), value)
|
||||
while exports['ps-buffs']:HasBuff("swimming") do
|
||||
Wait(500)
|
||||
SetPlayerStamina(PlayerId(), GetPlayerStamina(PlayerId()) + math.random(1,10))
|
||||
end
|
||||
SetSwimMultiplierForPlayer(PlayerId(), 1.0)
|
||||
hasSwimmingBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('SwimmingBuffEffect', SwimmingBuffEffect)
|
||||
|
||||
--- Method to add health buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of HP the player will gain over time
|
||||
|
||||
local hasHealthBuffActive = false
|
||||
local function AddHealthBuff(time, value)
|
||||
AddBuff("super-health", time)
|
||||
if not hasHealthBuffActive then
|
||||
hasHealthBuffActive = true
|
||||
CreateThread(function()
|
||||
while HasBuff("super-health") do
|
||||
Wait(5000)
|
||||
if GetEntityHealth(PlayerPedId()) < 200 then
|
||||
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + value)
|
||||
end
|
||||
end
|
||||
hasHealthBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('AddHealthBuff', AddHealthBuff)
|
||||
|
||||
--- Method to add armor buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of Armor the player will gain over time
|
||||
|
||||
local hasArmorBuffActive = false
|
||||
local function AddArmorBuff(time, value)
|
||||
AddBuff("super-armor", time)
|
||||
if not hasArmorBuffActive then
|
||||
hasArmorBuffActive = true
|
||||
CreateThread(function()
|
||||
while HasBuff("super-armor") do
|
||||
Wait(5000)
|
||||
if GetPedArmour(PlayerPedId()) < 100 then
|
||||
SetPedArmour(PlayerPedId(), GetPedArmour(PlayerPedId()) + value)
|
||||
end
|
||||
end
|
||||
hasArmorBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('AddArmorBuff', AddArmorBuff)
|
||||
|
||||
--- Method to add stress buff to player
|
||||
--- @param time - Time in ms the stress buff will be active
|
||||
--- @param value - The amount of stress the player will lose every 5 seconds
|
||||
local hasStressBuffActive = false
|
||||
local function AddStressBuff(time, value)
|
||||
AddBuff("super-stress", time)
|
||||
if not hasStressBuffActive then
|
||||
hasStressBuffActive = true
|
||||
CreateThread(function()
|
||||
while HasBuff("super-stress") do
|
||||
Wait(5000)
|
||||
TriggerServerEvent("hud:server:RelieveStress", value)
|
||||
end
|
||||
hasStressBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('AddStressBuff', AddStressBuff)
|
26
resources/[ps]/ps-buffs/fxmanifest.lua
Normal file
26
resources/[ps]/ps-buffs/fxmanifest.lua
Normal file
@ -0,0 +1,26 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
name "ps-buffs"
|
||||
description "Buff tracker for qbcore"
|
||||
author "Idris"
|
||||
version "0.0.1"
|
||||
|
||||
lua54 'yes'
|
||||
use_fxv2_oal 'yes'
|
||||
|
||||
shared_scripts {
|
||||
'shared/config.lua',
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'client/*.lua'
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'server/*.lua'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
'qb-core'
|
||||
}
|
194
resources/[ps]/ps-buffs/server/main.lua
Normal file
194
resources/[ps]/ps-buffs/server/main.lua
Normal file
@ -0,0 +1,194 @@
|
||||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
local playerBuffs = {}
|
||||
local next = next
|
||||
|
||||
--- Adds a buff to player
|
||||
--- @param citizenID string - Player identifier
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @param time number | nil - Optional time to add or how long you want buff to be
|
||||
--- @return bool
|
||||
local function AddBuff(sourceID, citizenID, buffName, time)
|
||||
local buffData = Config.Buffs[buffName]
|
||||
-- Check if we were given a correct buff name
|
||||
if buffData == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
-- If the player had no buffs at all then add them to the table
|
||||
if not playerBuffs[citizenID] then
|
||||
playerBuffs[citizenID] = {}
|
||||
end
|
||||
|
||||
local maxTime = buffData.maxTime
|
||||
|
||||
-- If the player didnt already have the buff requested set it to time or maxTime
|
||||
if not playerBuffs[citizenID][buffName] then
|
||||
local buffTime = maxTime
|
||||
if time then
|
||||
buffTime = time
|
||||
end
|
||||
|
||||
playerBuffs[citizenID][buffName] = buffTime
|
||||
|
||||
-- Since the player didnt already have this buff tell the front end to show it
|
||||
if buffData.type == 'buff' then
|
||||
-- Call client event to send nui to front end to start showing buff
|
||||
TriggerClientEvent('hud:client:BuffEffect', sourceID, {
|
||||
buffName = buffName,
|
||||
display = true,
|
||||
iconName = buffData.iconName,
|
||||
iconColor = buffData.iconColor,
|
||||
progressColor = buffData.progressColor,
|
||||
progressValue = (buffTime * 100) / buffData.maxTime,
|
||||
})
|
||||
else
|
||||
-- Call client event to send nui to front end to start showing enhancement
|
||||
TriggerClientEvent('hud:client:EnhancementEffect', sourceID, {
|
||||
display = true,
|
||||
enhancementName = buffName,
|
||||
iconColor = buffData.iconColor
|
||||
})
|
||||
end
|
||||
|
||||
else
|
||||
-- Since the player already had a buff increase the buff time, but not higher than max buff time
|
||||
local newTime = playerBuffs[citizenID][buffName] + time
|
||||
|
||||
if newTime > maxTime then
|
||||
newTime = maxTime
|
||||
end
|
||||
|
||||
playerBuffs[citizenID][buffName] = newTime
|
||||
end
|
||||
|
||||
return true
|
||||
end exports('AddBuff', AddBuff)
|
||||
|
||||
--- Removes a buff from provided player
|
||||
--- @param citizenID string - Player identifier
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @return bool - Success of removing the player buff
|
||||
local function Removebuff(citizenID, buffName)
|
||||
local buffData = Config.Buffs[buffName]
|
||||
if playerBuffs[citizenID] and playerBuffs[citizenID][buffName] then
|
||||
|
||||
playerBuffs[citizenID][buffName] = nil
|
||||
|
||||
local player = QBCore.Functions.GetPlayerByCitizenId(citizenID)
|
||||
local sourceID = nil
|
||||
|
||||
if player then
|
||||
sourceID = player.PlayerData.source
|
||||
end
|
||||
|
||||
-- Check if player is online
|
||||
if sourceID then
|
||||
-- Send a nui call to front end to stop showing icon
|
||||
if buffData.type == 'buff' then
|
||||
-- Call client event to send nui to front end to stop showing buff
|
||||
TriggerClientEvent('hud:client:BuffEffect', sourceID, {
|
||||
display = false,
|
||||
buffName = buffName,
|
||||
})
|
||||
else
|
||||
-- Call client event to send nui to front end to stop showing enhancement
|
||||
TriggerClientEvent('hud:client:EnhancementEffect', sourceID, {
|
||||
display = false,
|
||||
enhancementName = buffName,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Check to see if that was the player's last buff
|
||||
-- If so, then remove the player from the table to ensure we dont loop them
|
||||
if next(playerBuffs[citizenID]) == nil then
|
||||
playerBuffs[citizenID] = nil
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end exports('RemoveBuff', RemoveBuff)
|
||||
|
||||
--- Method to fetch if player has buff with name and is not nil
|
||||
--- @param citizenID string - Player identifier
|
||||
--- @param buffName string - Name of the buff
|
||||
--- @return bool
|
||||
local function HasBuff(citizenID, buffName)
|
||||
if playerBuffs[citizenID] then
|
||||
return playerBuffs[citizenID][buffName] ~= nil
|
||||
end
|
||||
|
||||
return false
|
||||
end exports('HasBuff', HasBuff)
|
||||
|
||||
QBCore.Functions.CreateCallback('buffs:server:fetchBuffs', function(source, cb)
|
||||
local player = QBCore.Functions.GetPlayer(source)
|
||||
local citizenID = player.PlayerData.citizenid
|
||||
cb(playerBuffs[citizenID])
|
||||
end)
|
||||
|
||||
QBCore.Functions.CreateCallback('buffs:server:addBuff', function(source, cb, buffName, time)
|
||||
local player = QBCore.Functions.GetPlayer(source)
|
||||
local citizenID = player.PlayerData.citizenid
|
||||
cb(AddBuff(source, citizenID, buffName, time))
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
local function DecrementBuff(sourceID, citizenID, buffName, currentTime)
|
||||
local buffData = Config.Buffs[buffName]
|
||||
local updatedTime = currentTime - Config.TickTime
|
||||
|
||||
-- Buff ran out of time we need to remove it from the player
|
||||
if updatedTime <= 0 then
|
||||
-- Only need to update buffs since they show progress on client
|
||||
if buffData.type == 'buff' then
|
||||
-- Check if player is online
|
||||
if sourceID then
|
||||
-- Call client event to send nui to front end, progress at 0
|
||||
TriggerClientEvent('hud:client:BuffEffect', sourceID, {
|
||||
buffName = buffName,
|
||||
progressValue = 0,
|
||||
})
|
||||
end
|
||||
end
|
||||
Removebuff(citizenID, buffName)
|
||||
else
|
||||
playerBuffs[citizenID][buffName] = updatedTime
|
||||
-- Only need to update buffs since they show progress on client
|
||||
if buffData.type == 'buff' then
|
||||
-- Check if player is online
|
||||
if sourceID then
|
||||
-- Call client event to send nui to front end
|
||||
TriggerClientEvent('hud:client:BuffEffect', sourceID, {
|
||||
buffName = buffName,
|
||||
-- Progress value needs to be from 0 - 100
|
||||
progressValue = (updatedTime * 100) / buffData.maxTime,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Not proud but need to loop through all timers but decrement it
|
||||
-- Loop is good as long as we only loop players that have buffs
|
||||
-- We ensure that when removing any buff, we check to see if it was the player's last buff
|
||||
-- Then we remove that player from our table to ensure we dont loop them
|
||||
while true do
|
||||
for citizenID, buffTable in pairs(playerBuffs) do
|
||||
local player = QBCore.Functions.GetPlayerByCitizenId(citizenID)
|
||||
local sourceID = nil
|
||||
|
||||
if player then
|
||||
sourceID = player.PlayerData.source
|
||||
end
|
||||
|
||||
for buffName, currentTime in pairs(buffTable) do
|
||||
DecrementBuff(sourceID, citizenID, buffName, currentTime)
|
||||
end
|
||||
end
|
||||
|
||||
Wait(Config.TickTime)
|
||||
end
|
||||
end)
|
86
resources/[ps]/ps-buffs/shared/config.lua
Normal file
86
resources/[ps]/ps-buffs/shared/config.lua
Normal file
@ -0,0 +1,86 @@
|
||||
Config = Config or {}
|
||||
|
||||
-- How fast should the main loop over player buffs run and also how fast to send nui messages to the client
|
||||
Config.TickTime = 6000
|
||||
|
||||
-- "biohazard"
|
||||
-- "exclamation"
|
||||
-- "locationarrow"
|
||||
|
||||
-- "#FFD700" is gold
|
||||
-- '#FDE829' is yellow
|
||||
|
||||
Config.Buffs = {
|
||||
-- Buff Section -- Do not change type, even if you know what you are doing
|
||||
-- You are free to change the index names, but other scripts that relie on this config might need it
|
||||
['hacking'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'database',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
['intelligence'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'lightbulb',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
['luck'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'dollarsign',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
['stamina'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'wind',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
['strength'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'dumbbell',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
['swimming'] = {
|
||||
iconColor = "#ffffff",
|
||||
iconName = 'swimmer',
|
||||
maxTime = 3600000,
|
||||
progressColor = "#FFD700",
|
||||
type = 'buff'
|
||||
},
|
||||
|
||||
-- Enhancement Section -- Do not change type, even if you know what you are doing
|
||||
-- Do not change the index names (super names) as they are used in the front end to determine which icon to change color
|
||||
['super-hunger'] = {
|
||||
maxTime = 3600000,
|
||||
iconColor = '#FDE829',
|
||||
type = 'enhancement',
|
||||
},
|
||||
['super-thirst'] = {
|
||||
maxTime = 3600000,
|
||||
iconColor = '#FDE829',
|
||||
type = 'enhancement',
|
||||
},
|
||||
['super-health'] = {
|
||||
maxTime = 3600000,
|
||||
iconColor = '#FDE829',
|
||||
type = 'enhancement',
|
||||
},
|
||||
['super-armor'] = {
|
||||
maxTime = 3600000,
|
||||
iconColor = '#FDE829',
|
||||
type = 'enhancement',
|
||||
},
|
||||
['super-stress'] = {
|
||||
maxTime = 3600000,
|
||||
iconColor = '#FDE829',
|
||||
type = 'enhancement',
|
||||
}
|
||||
}
|
202
resources/[ps]/ps-buffs/snippets
Normal file
202
resources/[ps]/ps-buffs/snippets
Normal file
@ -0,0 +1,202 @@
|
||||
CREDITS: SNIPE OP GAMING AND FJAMZOO
|
||||
|
||||
--SOME SNIPPETS TO GO INTO PS-BUFFS/CLIENT LUA------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
HEALTH AND ARMOR----
|
||||
--- Method to add health buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of HP the player will gain over time
|
||||
|
||||
local hasHealthBuffActive = false
|
||||
local function AddHealthBuff(time, value)
|
||||
AddBuff("super-health", time)
|
||||
if not hasHealthBuffActive then
|
||||
hasHealthBuffActive = true
|
||||
CreateThread(function()
|
||||
while HasBuff("super-health") do
|
||||
Wait(5000)
|
||||
if GetEntityHealth(PlayerPedId()) < 200 then
|
||||
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + value)
|
||||
end
|
||||
end
|
||||
hasHealthBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('AddHealthBuff', AddHealthBuff)
|
||||
|
||||
--- Method to add armor buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of Armor the player will gain over time
|
||||
|
||||
local hasArmorBuffActive = false
|
||||
local function AddArmorBuff(time, value)
|
||||
AddBuff("super-armor", time)
|
||||
if not hasArmorBuffActive then
|
||||
hasArmorBuffActive = true
|
||||
CreateThread(function()
|
||||
while HasBuff("super-armor") do
|
||||
Wait(5000)
|
||||
if GetPedArmour(PlayerPedId()) < 100 then
|
||||
SetPedArmour(PlayerPedId(), GetPedArmour(PlayerPedId()) + value)
|
||||
end
|
||||
end
|
||||
hasArmorBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('AddArmorBuff', AddArmorBuff)
|
||||
|
||||
RegisterCommand("stambuff", function()
|
||||
AddHealthBuff(50000, math.random(1,5))
|
||||
Wait(1000)
|
||||
AddArmorBuff(50000, math.random(1,5))
|
||||
end)
|
||||
|
||||
|
||||
exports["ps-buffs"]:AddHealthBuff(time in ms, buff amoount)
|
||||
exports["ps-buffs"]:AddArmorBuff(time in ms, buff amoount)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
STAMINA BUFF--
|
||||
--- Method to add stamina buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of speed boost the player will recieve
|
||||
local hasStaminaBuffActive = false
|
||||
local function StaminaBuffEffect(time, value)
|
||||
AddBuff("stamina", time)
|
||||
if not hasStaminaBuffActive then
|
||||
hasStaminaBuffActive = true
|
||||
CreateThread(function()
|
||||
SetRunSprintMultiplierForPlayer(PlayerId(), value)
|
||||
while exports['ps-buffs']:HasBuff("stamina") do
|
||||
Wait(500)
|
||||
SetPlayerStamina(PlayerId(), GetPlayerStamina(PlayerId()) + math.random(1,10))
|
||||
end
|
||||
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
|
||||
hasStaminaBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('StaminaBuffEffect', StaminaBuffEffect)
|
||||
|
||||
|
||||
exports["ps-buffs"]:StaminaBuffEffect(time in ms, buff amoount)
|
||||
|
||||
|
||||
Stamina buff with values like time and buff amount
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SWIMMING BUFF
|
||||
|
||||
--- Method to add swimming buff to player
|
||||
--- @param time - Time in ms the health buff will be active
|
||||
--- @param value - The amount of swimming speed boost the player will recieve
|
||||
local hasSwimmingBuffActive = false
|
||||
local function SwimmingBuffEffect(time, value)
|
||||
AddBuff("swimming", time)
|
||||
if not hasSwimmingBuffActive then
|
||||
hasSwimmingBuffActive = true
|
||||
CreateThread(function()
|
||||
SetSwimMultiplierForPlayer(PlayerId(), value)
|
||||
while exports['ps-buffs']:HasBuff("swimming") do
|
||||
Wait(500)
|
||||
SetPlayerStamina(PlayerId(), GetPlayerStamina(PlayerId()) + math.random(1,10))
|
||||
end
|
||||
SetSwimMultiplierForPlayer(PlayerId(), 1.0)
|
||||
hasSwimmingBuffActive = false
|
||||
end)
|
||||
end
|
||||
end exports('SwimmingBuffEffect', SwimmingBuffEffect)
|
||||
|
||||
|
||||
exports["ps-buffs"]:SwimmingBuffEffect(time in ms, buff ammount)
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Hunger and Thirst Buff!!
|
||||
Notes. You need to replace the following snippets in core. Do not ADD
|
||||
|
||||
If you add this block of code instead of replacing, you will get error In server console with hungerRate is nil or thirstRate is nil!
|
||||
|
||||
--------------qb-core/client/loops.lua---------------------------------------------------------------------------
|
||||
CreateThread(function()
|
||||
while true do
|
||||
local sleep = 0
|
||||
if LocalPlayer.state.isLoggedIn then
|
||||
sleep = (1000 * 60) * QBCore.Config.UpdateInterval
|
||||
local hungerRate = 0
|
||||
local thirstRate = 0
|
||||
if exports["ps-buffs"]:HasBuff("super-hunger") then hungerRate = QBCore.Config.Player.HungerRate/2 else hungerRate = QBCore.Config.Player.HungerRate end
|
||||
if exports["ps-buffs"]:HasBuff("super-thirst") then thirstRate = QBCore.Config.Player.ThirstRate/2 else thirstRate = QBCore.Config.Player.ThirstRate end
|
||||
TriggerServerEvent('QBCore:UpdatePlayer', hungerRate, thirstRate)
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
qb-core/server/events.lua - Replace the event with this new event--------------------------------------------------------
|
||||
RegisterNetEvent('QBCore:UpdatePlayer', function(hungerRate, thirstRate)
|
||||
print('Updating Player', hungerRate, thirstRate)
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
local newHunger = Player.PlayerData.metadata['hunger'] - hungerRate
|
||||
local newThirst = Player.PlayerData.metadata['thirst'] - thirstRate
|
||||
if newHunger <= 0 then
|
||||
newHunger = 0
|
||||
end
|
||||
if newThirst <= 0 then
|
||||
newThirst = 0
|
||||
end
|
||||
Player.Functions.SetMetaData('thirst', newThirst)
|
||||
Player.Functions.SetMetaData('hunger', newHunger)
|
||||
TriggerClientEvent('hud:client:UpdateNeeds', src, newHunger, newThirst)
|
||||
Player.Functions.Save()
|
||||
end)
|
||||
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
|
||||
|
||||
|
||||
Replace this in qb-core/client/events.lua---------------------------------------------------------------------------------------------
|
||||
RegisterNetEvent('QBCore:Player:UpdatePlayerData', function()
|
||||
local hungerRate = 0
|
||||
local thirstRate = 0
|
||||
if exports["ps-buffs"]:HasBuff("super-hunger") then hungerRate = QBCore.Config.Player.HungerRate/2 else hungerRate = QBCore.Config.Player.HungerRate end
|
||||
if exports["ps-buffs"]:HasBuff("super-thirst") then thirstRate = QBCore.Config.Player.ThirstRate/2 else thirstRate = QBCore.Config.Player.ThirstRate end
|
||||
TriggerServerEvent('QBCore:UpdatePlayer', hungerRate, thirstRate)
|
||||
end)
|
||||
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
21
resources/[ps]/ps-dispatch/LICENSE
Normal file
21
resources/[ps]/ps-dispatch/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Wild Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
13
resources/[ps]/ps-dispatch/README.md
Normal file
13
resources/[ps]/ps-dispatch/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# ps-dispatch
|
||||
[Discord Server](https://discord.gg/wilddevelopment)
|
||||
## Wild Development Presents
|
||||
Shot Fire
|
||||

|
||||
İn Vehicle
|
||||

|
||||
Officer Down Preview İn PD Job
|
||||

|
||||
Officer Down Preview İn EMS Job
|
||||

|
||||
|
||||
|
239
resources/[ps]/ps-dispatch/client/cl_commands.lua
Normal file
239
resources/[ps]/ps-dispatch/client/cl_commands.lua
Normal file
@ -0,0 +1,239 @@
|
||||
|
||||
local playAnim = false
|
||||
local phoneProp = 0
|
||||
local phoneModel = Config.PhoneModel
|
||||
|
||||
|
||||
-- Item checks to return whether or not the client has a phone or not
|
||||
local function HasPhone()
|
||||
return QBCore.Functions.HasItem("phone")
|
||||
end
|
||||
|
||||
|
||||
-- Loads the animdict so we can execute it on the ped
|
||||
local function loadAnimDict(dict)
|
||||
RequestAnimDict(dict)
|
||||
|
||||
while not HasAnimDictLoaded(dict) do
|
||||
Wait(0)
|
||||
end
|
||||
end
|
||||
|
||||
local function DeletePhone()
|
||||
if phoneProp ~= 0 then
|
||||
DeleteObject(phoneProp)
|
||||
phoneProp = 0
|
||||
end
|
||||
end
|
||||
|
||||
local function NewPropWhoDis()
|
||||
DeletePhone()
|
||||
RequestModel(phoneModel)
|
||||
while not HasModelLoaded(phoneModel) do
|
||||
Wait(1)
|
||||
end
|
||||
phoneProp = CreateObject(phoneModel, 1.0, 1.0, 1.0, 1, 1, 0)
|
||||
|
||||
local bone = GetPedBoneIndex(PlayerPedId(), 28422)
|
||||
if phoneModel == Config.PhoneModel then
|
||||
AttachEntityToEntity(phoneProp, PlayerPedId(), bone, 0.0, 0.0, 0.0, 15.0, 0.0, 0.0, 1, 1, 0, 0, 2, 1)
|
||||
else
|
||||
AttachEntityToEntity(phoneProp, PlayerPedId(), bone, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1, 1, 0, 0, 2, 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Does the actual animation of the animation when calling 911
|
||||
local function PhoneCallAnim()
|
||||
loadAnimDict("cellphone@")
|
||||
local ped = PlayerPedId()
|
||||
CreateThread(function()
|
||||
NewPropWhoDis()
|
||||
playAnim = true
|
||||
while playAnim do
|
||||
if not IsEntityPlayingAnim(ped, "cellphone@", 'cellphone_text_to_call', 3) then
|
||||
TaskPlayAnim(ped, "cellphone@", 'cellphone_text_to_call', 3.0, 3.0, -1, 50, 0, false, false, false)
|
||||
end
|
||||
Wait(100)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
-- Regular 911 call that goes straight to the Police
|
||||
RegisterCommand('114', function(source, args, rawCommand)
|
||||
local msg = rawCommand:sub(5)
|
||||
if string.len(msg) > 0 then
|
||||
if not exports['qb-policejob']:IsHandcuffed() then
|
||||
if HasPhone() then
|
||||
PhoneCallAnim()
|
||||
Wait(math.random(3,8) * 1000)
|
||||
playAnim = false
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "114call", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "114",
|
||||
firstStreet = locationInfo,
|
||||
priority = 2, -- priority
|
||||
name = plyData.charinfo.firstname:sub(1,1):upper()..plyData.charinfo.firstname:sub(2).. " ".. plyData.charinfo.lastname:sub(1,1):upper()..plyData.charinfo.lastname:sub(2),
|
||||
number = plyData.charinfo.phone,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Ingående opkald", -- message
|
||||
information = msg,
|
||||
job = {"police", "ambulance"} -- jobs that will get the alerts
|
||||
})
|
||||
Wait(1000)
|
||||
DeletePhone()
|
||||
StopEntityAnim(PlayerPedId(), 'cellphone_text_to_call', "cellphone@", 3)
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe uden en telefon!", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe når du er i håndjern..", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify('Angiv en årsag efter 114', "success")
|
||||
end
|
||||
end, false)
|
||||
|
||||
RegisterCommand('114a', function(source, args, rawCommand)
|
||||
local msg = rawCommand:sub(5)
|
||||
if string.len(msg) > 0 then
|
||||
if not exports['qb-policejob']:IsHandcuffed() then
|
||||
if HasPhone() then
|
||||
PhoneCallAnim()
|
||||
Wait(math.random(3,8) * 1000)
|
||||
playAnim = false
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "114call", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "114",
|
||||
firstStreet = locationInfo,
|
||||
priority = 2, -- priority
|
||||
name = "Anonym",
|
||||
number = "Skjult Nummer",
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Ingående anonymt opkald", -- message
|
||||
information = msg,
|
||||
job = {"police", "ambulance"} -- jobs that will get the alerts
|
||||
})
|
||||
Wait(1000)
|
||||
DeletePhone()
|
||||
StopEntityAnim(PlayerPedId(), 'cellphone_text_to_call', "cellphone@", 3)
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe uden en telefon!", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe når du er i håndjern..", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify('Angiv en årsag efter 114', "success")
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Regular 311 call that goes straight to the Police
|
||||
RegisterCommand('112', function(source, args, rawCommand)
|
||||
local msg = rawCommand:sub(5)
|
||||
if string.len(msg) > 0 then
|
||||
if not exports['qb-policejob']:IsHandcuffed() then
|
||||
if HasPhone() then
|
||||
PhoneCallAnim()
|
||||
Wait(math.random(3,8) * 1000)
|
||||
playAnim = false
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "112call", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "112",
|
||||
firstStreet = locationInfo,
|
||||
priority = 2, -- priority
|
||||
name = plyData.charinfo.firstname:sub(1,1):upper()..plyData.charinfo.firstname:sub(2).. " ".. plyData.charinfo.lastname:sub(1,1):upper()..plyData.charinfo.lastname:sub(2),
|
||||
number = plyData.charinfo.phone,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Ingående opkald", -- message
|
||||
information = msg,
|
||||
job = {"police", "ambulance"} -- jobs that will get the alerts
|
||||
})
|
||||
Wait(1000)
|
||||
DeletePhone()
|
||||
StopEntityAnim(PlayerPedId(), 'cellphone_text_to_call', "cellphone@", 3)
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe uden en telefon!", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe når du er i håndjern..", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify('Angiv en årsag efter 112', "success")
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Regular 311 call that goes straight to the Police
|
||||
RegisterCommand('112a', function(source, args, rawCommand)
|
||||
local msg = rawCommand:sub(5)
|
||||
if string.len(msg) > 0 then
|
||||
if not exports['qb-policejob']:IsHandcuffed() then
|
||||
if HasPhone() then
|
||||
PhoneCallAnim()
|
||||
Wait(math.random(3,8) * 1000)
|
||||
playAnim = false
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "112call", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "112",
|
||||
firstStreet = locationInfo,
|
||||
priority = 2, -- priority
|
||||
name = "Anonym",
|
||||
number = "Skjult Nummer",
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Ingående anonymt opkald", -- message
|
||||
information = msg,
|
||||
job = {"police", "ambulance"} -- jobs that will get the alerts
|
||||
})
|
||||
Wait(1000)
|
||||
DeletePhone()
|
||||
StopEntityAnim(PlayerPedId(), 'cellphone_text_to_call', "cellphone@", 3)
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe uden en telefon!", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify("Du kan ikke ringe når du er i håndjern..", "error", 4500)
|
||||
end
|
||||
else
|
||||
QBCore.Functions.Notify('Angiv en årsag efter 112a', "success")
|
||||
end
|
||||
end, false)
|
||||
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
TriggerEvent('chat:addSuggestion', '/114', 'Send en besked til politiet.', {{ name="besked", help="Besked til politiet."}})
|
||||
TriggerEvent('chat:addSuggestion', '/114a', 'Send en anonym besked til politiet.', {{ name="besked", help="Anonym besked til politiet."}})
|
||||
TriggerEvent('chat:addSuggestion', '/112', 'Send en besked til EMS.', {{ name="besked", help="Besked til EMS."}})
|
||||
TriggerEvent('chat:addSuggestion', '/112a', 'Send en anonym besked til EMS.', {{ name="besked", help="Anonym besked til EMS."}})
|
||||
end)
|
773
resources/[ps]/ps-dispatch/client/cl_events.lua
Normal file
773
resources/[ps]/ps-dispatch/client/cl_events.lua
Normal file
@ -0,0 +1,773 @@
|
||||
local WeaponTable = {
|
||||
[584646201] = "CLASS 2: AP-Pistol",
|
||||
[453432689] = "CLASS 1: Pistol",
|
||||
[3219281620] = "CLASS 1: Pistol MK2",
|
||||
[1593441988] = "CLASS 1: Combat Pistol",
|
||||
[-1716589765] = "CLASS 1: Heavy Pistol",
|
||||
[-1076751822] = "CLASS 1: SNS-Pistol",
|
||||
[-771403250] = "CLASS 2: Desert Eagle",
|
||||
[137902532] = "CLASS 2: Vintage Pistol",
|
||||
[-598887786] = "CLASS 2: Marksman Pistol",
|
||||
[-1045183535] = "CLASS 2: Revolver",
|
||||
[911657153] = "Taser",
|
||||
[324215364] = "CLASS 2: Micro-SMG",
|
||||
[-619010992] = "CLASS 2: Machine-Pistol",
|
||||
[736523883] = "CLASS 2: SMG",
|
||||
[2024373456] = "CLASS 2: SMG MK2",
|
||||
[-270015777] = "CLASS 2: Assault SMG",
|
||||
[171789620] = "CLASS 2: Combat PDW",
|
||||
[-1660422300] = "CLASS 4: Combat MG",
|
||||
[3686625920] = "CLASS 4: Combat MG MK2",
|
||||
[1627465347] = "CLASS 4: Gusenberg",
|
||||
[-1121678507] = "CLASS 2: Mini SMG",
|
||||
[-1074790547] = "CLASS 3: Assaultrifle",
|
||||
[961495388] = "CLASS 3: Assaultrifle MK2",
|
||||
[-2084633992] = "CLASS 3: Carbinerifle",
|
||||
[4208062921] = "CLASS 3: Carbinerifle MK2",
|
||||
[3520460075] = "CLASS 3: Carbinerifle MK2",
|
||||
[-1357824103] = "CLASS 3: Advancedrifle",
|
||||
[-1063057011] = "CLASS 3: Specialcarbine",
|
||||
[2132975508] = "CLASS 3: Bulluprifle",
|
||||
[1649403952] = "CLASS 3: Compactrifle",
|
||||
[100416529] = "CLASS 4: Sniperrifle",
|
||||
[205991906] = "CLASS 4: Heavy Sniper",
|
||||
[177293209] = "CLASS 4: Heavy Sniper MK2",
|
||||
[-952879014] = "CLASS 4: Marksmanrifle",
|
||||
[487013001] = "CLASS 2: Pumpshotgun",
|
||||
[2017895192] = "CLASS 2: Sawnoff Shotgun",
|
||||
[-1654528753] = "CLASS 3: Bullupshotgun",
|
||||
[-494615257] = "CLASS 3: Assaultshotgun",
|
||||
[-1466123874] = "CLASS 3: Musket",
|
||||
[984333226] = "CLASS 3: Heavyshotgun",
|
||||
[-275439685] = "CLASS 2: Doublebarrel Shotgun",
|
||||
[317205821] = "CLASS 2: Autoshotgun",
|
||||
[-1568386805] = "CLASS 5: GRENADE LAUNCHER",
|
||||
[-1312131151] = "CLASS 5: RPG",
|
||||
[125959754] = "CLASS 5: Compactlauncher",
|
||||
}
|
||||
local function VehicleTheft(vehicle)
|
||||
local vehdata = vehicleData(vehicle)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local heading = getCardinalDirectionFromHeading()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "vehicletheft", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-35",
|
||||
firstStreet = locationInfo,
|
||||
model = vehdata.name, -- vehicle name
|
||||
plate = vehdata.plate, -- vehicle plate
|
||||
priority = 2,
|
||||
firstColor = vehdata.colour, -- vehicle color
|
||||
heading = heading,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('vehicletheft'),
|
||||
job = { "police" }
|
||||
})
|
||||
end
|
||||
|
||||
exports('VehicleTheft', VehicleTheft)
|
||||
|
||||
local function VehicleShooting(vehdata)
|
||||
local Player = QBCore.Functions.GetPlayerData()
|
||||
if Player.job.name == "police" or Player.job.name == "ambulance" then
|
||||
if Player.job.onduty then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local vehicle = QBCore.Functions.GetClosestVehicle()
|
||||
local vehdata = vehicleData(vehicle)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local heading = getCardinalDirectionFromHeading()
|
||||
local gender = GetPedGender()
|
||||
local doorCount = 0
|
||||
|
||||
local PlayerPed = PlayerPedId()
|
||||
local CurrentWeapon = GetSelectedPedWeapon(PlayerPed)
|
||||
local weapon = WeaponTable[CurrentWeapon] or "UKENDT"
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_pside_f') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_pside_r') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_dside_f') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_dside_r') ~= -1 then doorCount = doorCount + 1 end
|
||||
if doorCount == 2 then doorCount = "To-dørs" elseif doorCount == 3 then doorCount = "Tre-dørs" elseif doorCount == 4 then doorCount = "Fire-dørs" else doorCount = "UKENDT" end
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "vehicleshots", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-60",
|
||||
firstStreet = locationInfo,
|
||||
model = vehdata.name,
|
||||
plate = vehdata.plate,
|
||||
gender = gender,
|
||||
weapon = weapon,
|
||||
doorCount = doorCount,
|
||||
priority = 2,
|
||||
firstColor = vehdata.colour,
|
||||
heading = heading,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('vehicleshots'),
|
||||
job = { "police" }
|
||||
})
|
||||
end
|
||||
|
||||
exports('VehicleShooting', VehicleShooting)
|
||||
|
||||
-- Meter Robbery
|
||||
local function MeterRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "meterrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('meterrobbery'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('MeterRobbery', MeterRobbery)
|
||||
|
||||
-- ATM Robbery
|
||||
local function ATMRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "atmrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('atmrobbery'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('ATMRobbery', ATMRobbery)
|
||||
|
||||
local function Shooting()
|
||||
local Player = QBCore.Functions.GetPlayerData()
|
||||
if Player.job.name == "police" or Player.job.name == "ambulance" then
|
||||
if Player.job.onduty then
|
||||
return
|
||||
end
|
||||
end
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
local PlayerPed = PlayerPedId()
|
||||
local CurrentWeapon = GetSelectedPedWeapon(PlayerPed)
|
||||
local weapon = WeaponTable[CurrentWeapon] or "UNKNOWN"
|
||||
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "shooting", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-11",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
weapon = weapon,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2,
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('shooting'),
|
||||
job = { "police" }
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
exports('Shooting', Shooting)
|
||||
|
||||
local function SpeedingVehicle(vehdata)
|
||||
local Player = QBCore.Functions.GetPlayerData()
|
||||
if Player.job.name == "police" or Player.job.name == "ambulance" then
|
||||
if Player.job.onduty then
|
||||
return
|
||||
end
|
||||
end
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local heading = getCardinalDirectionFromHeading()
|
||||
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "speeding", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-11",
|
||||
firstStreet = locationInfo,
|
||||
model = vehdata.name,
|
||||
plate = vehdata.plate,
|
||||
priority = 2,
|
||||
firstColor = vehdata.colour,
|
||||
heading = heading,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('speeding'),
|
||||
job = { "police" }
|
||||
})
|
||||
end
|
||||
|
||||
exports('SpeedingVehicle', SpeedingVehicle)
|
||||
|
||||
local function Fight()
|
||||
local Player = QBCore.Functions.GetPlayerData()
|
||||
if Player.job.name == "police" or Player.job.name == "ambulance" then
|
||||
if Player.job.onduty then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "fight", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-10",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2,
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('melee'),
|
||||
job = { "police" }
|
||||
})
|
||||
end
|
||||
|
||||
exports('Fight', Fight)
|
||||
|
||||
local function InjuriedPerson()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "civdown", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-69",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('persondown'), -- message
|
||||
job = { "ambulance" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('InjuriedPerson', InjuriedPerson)
|
||||
|
||||
local function DeceasedPerson()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "civdead", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-69",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Civil afgik ved døden", -- message
|
||||
job = { "ambulance" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('DeceasedPerson', DeceasedPerson)
|
||||
|
||||
local function StoreRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "storerobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('storerobbery'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('StoreRobbery', StoreRobbery)
|
||||
|
||||
local function FleecaBankRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "bankrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('fleecabank'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('FleecaBankRobbery', FleecaBankRobbery)
|
||||
|
||||
local function PaletoBankRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "paletobankrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('paletobank'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('PaletoBankRobbery', PaletoBankRobbery)
|
||||
|
||||
local function PacificBankRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "pacificbankrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('pacificbank'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('PacificBankRobbery', PacificBankRobbery)
|
||||
|
||||
local function PrisonBreak()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "prisonbreak", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('prisonbreak'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('PrisonBreak', PrisonBreak)
|
||||
|
||||
local function VangelicoRobbery(camId)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
PlaySound(-1, "Lose_1st", "GTAO_FM_Events_Soundset", 0, 0, 1)
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "vangelicorobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
camId = camId,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('vangelico'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('VangelicoRobbery', VangelicoRobbery)
|
||||
|
||||
local function HouseRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "houserobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('houserobbery'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('HouseRobbery', HouseRobbery)
|
||||
|
||||
local function YachtHeist()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "yachtheist", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-65",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('yachtheist'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('YachtHeist', YachtHeist)
|
||||
|
||||
local function DrugSale()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "suspicioushandoff", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-13",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('drugsell'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('DrugSale', DrugSale)
|
||||
|
||||
|
||||
-- for rcore_gangs, haven't tested server side exports so made this instead. Remove if you do not need :)
|
||||
RegisterNetEvent('ps-dispatch:client:drugsale', function()
|
||||
DrugSale()
|
||||
end)
|
||||
|
||||
local function CarJacking(vehicle)
|
||||
local vehdata = vehicleData(vehicle)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local heading = getCardinalDirectionFromHeading()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "carjack", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-35",
|
||||
firstStreet = locationInfo,
|
||||
model = vehdata.name, -- vehicle name
|
||||
plate = vehdata.plate, -- vehicle plate
|
||||
priority = 2,
|
||||
firstColor = vehdata.colour, -- vehicle color
|
||||
heading = heading,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('carjacking'),
|
||||
job = { "police" }
|
||||
})
|
||||
end
|
||||
|
||||
exports('CarJacking', CarJacking)
|
||||
|
||||
local function OfficerDown()
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local callsign = QBCore.Functions.GetPlayerData().metadata["callsign"]
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "officerdown", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-99",
|
||||
firstStreet = locationInfo,
|
||||
name = "BETJENT - " .. plyData.charinfo.firstname:sub(1, 1):upper() .. plyData.charinfo.firstname:sub(2) .. " " .. plyData.charinfo.lastname:sub(1, 1):upper() .. plyData.charinfo.lastname:sub(2),
|
||||
model = nil,
|
||||
plate = nil,
|
||||
callsign = callsign,
|
||||
priority = 1, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('officerdown'), -- message
|
||||
job = { "ambulance", "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('OfficerDown', OfficerDown)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:officerdown", function ()
|
||||
OfficerDown()
|
||||
end)
|
||||
|
||||
local function EmsDown()
|
||||
local plyData = QBCore.Functions.GetPlayerData()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local callsign = QBCore.Functions.GetPlayerData().metadata["callsign"]
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "emsdown", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-99",
|
||||
firstStreet = locationInfo,
|
||||
name = "EMS - " .. plyData.charinfo.firstname:sub(1, 1):upper() .. plyData.charinfo.firstname:sub(2) .. " " .. plyData.charinfo.lastname:sub(1, 1):upper() .. plyData.charinfo.lastname:sub(2),
|
||||
model = nil,
|
||||
plate = nil,
|
||||
callsign = callsign,
|
||||
priority = 1, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('emsdown'), -- message
|
||||
job = { "ambulance", "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('EmsDown', EmsDown)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:emsdown", function ()
|
||||
EmsDown()
|
||||
end)
|
||||
|
||||
local function Explosion()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "explosion", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-80",
|
||||
firstStreet = locationInfo,
|
||||
gender = nil,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = "Eksplosion meldt", -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('Explosion', Explosion)
|
||||
|
||||
local function SuspiciousActivity()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchcodename = "susactivity", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-66",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('susactivity'), -- message
|
||||
job = { "police" } -- jobs that will get the alerts
|
||||
})
|
||||
end
|
||||
|
||||
exports('SuspiciousActivity', SuspiciousActivity)
|
||||
|
||||
local function CustomAlert(data)
|
||||
|
||||
local coords = data.coords or vec3(0.0, 0.0, 0.0)
|
||||
local gender = GetPedGender()
|
||||
if not data.gender then gender = nil end
|
||||
local job = { "police" }
|
||||
if data.job then job = data.job end
|
||||
|
||||
|
||||
local locationInfo = getStreetandZone(coords)
|
||||
TriggerServerEvent("dispatch:server:notify", {
|
||||
dispatchCode = data.dispatchCode or "INGEN",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = data.model or nil,
|
||||
plate = data.plate or nil,
|
||||
priority = data.priority or 2, -- priority
|
||||
firstColor = data.firstColor or nil,
|
||||
camId = data.camId or nil,
|
||||
callsign = data.callsign or nil,
|
||||
name = data.name or nil,
|
||||
doorCount = data.doorCount or nil,
|
||||
heading = data.heading or nil,
|
||||
automaticGunfire = data.automaticGunfire or false,
|
||||
origin = {
|
||||
x = coords.x,
|
||||
y = coords.y,
|
||||
z = coords.z
|
||||
},
|
||||
dispatchMessage = data.message or "",
|
||||
job = job,
|
||||
alert = {
|
||||
displayCode = data.dispatchCode or "INGEN",
|
||||
description = data.description or "",
|
||||
radius = data.radius or 0,
|
||||
recipientList = job,
|
||||
blipSprite = data.sprite or 1,
|
||||
blipColour = data.color or 1,
|
||||
blipScale = data.scale or 0.5,
|
||||
blipLength = data.length or 2,
|
||||
sound = data.sound or "Lose_1st",
|
||||
sound2 = data.sound2 or "GTAO_FM_Events_Soundset",
|
||||
offset = data.offset or "false",
|
||||
blipflash = data.flash or "false"
|
||||
}
|
||||
})
|
||||
end
|
||||
exports('CustomAlert', CustomAlert)
|
224
resources/[ps]/ps-dispatch/client/cl_extraalerts.lua
Normal file
224
resources/[ps]/ps-dispatch/client/cl_extraalerts.lua
Normal file
@ -0,0 +1,224 @@
|
||||
---------------------------
|
||||
----rainmad scripts--------
|
||||
---------------------------
|
||||
|
||||
local function ArtGalleryRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "artgalleryrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('artgalleryrobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('ArtGalleryRobbery', ArtGalleryRobbery)
|
||||
|
||||
local function HumaneRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "humanelabsrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('humanerobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('HumaneRobbery', HumaneRobbery)
|
||||
|
||||
local function TrainRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "trainrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('trainrobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('TrainRobbery', TrainRobbery)
|
||||
|
||||
local function VanRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "vanrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('vanrobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('VanRobbery', VanRobbery)
|
||||
|
||||
local function UndergroundRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "undergroundrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('underground'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('UndergroundRobbery', UndergroundRobbery)
|
||||
|
||||
local function DrugBoatRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "drugboatrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('drugboatrobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('DrugBoatRobbery', DrugBoatRobbery)
|
||||
|
||||
local function UnionRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "unionrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-90",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('unionrobbery'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('UnionRobbery', UnionRobbery)
|
||||
|
||||
local function CarBoosting(vehicle)
|
||||
local vehdata = vehicleData(vehicle)
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "carboosting", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-50",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = vehdata.name,
|
||||
plate = vehdata.plate,
|
||||
priority = 2,
|
||||
firstColor = vehdata.colour,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = _U('carboosting'), -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('CarBoosting', CarBoosting)
|
||||
|
||||
---------------------------
|
||||
---- ps-signrobbery -------
|
||||
---------------------------
|
||||
|
||||
local function SignRobbery()
|
||||
local currentPos = GetEntityCoords(PlayerPedId())
|
||||
local locationInfo = getStreetandZone(currentPos)
|
||||
local gender = GetPedGender()
|
||||
TriggerServerEvent("dispatch:server:notify",{
|
||||
dispatchcodename = "signrobbery", -- has to match the codes in sv_dispatchcodes.lua so that it generates the right blip
|
||||
dispatchCode = "10-35",
|
||||
firstStreet = locationInfo,
|
||||
gender = gender,
|
||||
model = nil,
|
||||
plate = nil,
|
||||
priority = 2, -- priority
|
||||
firstColor = nil,
|
||||
automaticGunfire = false,
|
||||
origin = {
|
||||
x = currentPos.x,
|
||||
y = currentPos.y,
|
||||
z = currentPos.z
|
||||
},
|
||||
dispatchMessage = 'Skiltetyveri begået', -- message
|
||||
job = {"police"} -- jobs that will get the alerts
|
||||
})
|
||||
end exports('SignRobbery', SignRobbery)
|
57
resources/[ps]/ps-dispatch/client/cl_loops.lua
Normal file
57
resources/[ps]/ps-dispatch/client/cl_loops.lua
Normal file
@ -0,0 +1,57 @@
|
||||
CreateThread(function()
|
||||
local vehicleWhitelist = {[0]=true,[1]=true,[2]=true,[3]=true,[4]=true,[5]=true,[6]=true,[7]=true,[8]=true,[9]=true,[10]=true,[11]=true,[12]=true,[17]=true,[19]=true,[20]=true}
|
||||
local sleep = 100
|
||||
while true do
|
||||
playerPed = PlayerPedId()
|
||||
if (not isPlayerWhitelisted or Config.Debug) then
|
||||
for k, v in pairs(Config.Timer) do
|
||||
if v > 0 then Config.Timer[k] = v - 1 end
|
||||
end
|
||||
if GetVehiclePedIsUsing(playerPed) ~= 0 then
|
||||
local vehicle = GetVehiclePedIsUsing(playerPed, true)
|
||||
if vehicleWhitelist[GetVehicleClass(vehicle)] then
|
||||
local driver = GetPedInVehicleSeat(vehicle, -1)
|
||||
if Config.Timer['Shooting'] == 0 and not BlacklistedWeapon(playerPed) and not IsPedCurrentWeaponSilenced(playerPed) and IsPedArmed(playerPed, 4) then
|
||||
sleep = 10
|
||||
if IsPedShooting(playerPed) then
|
||||
local vehicle = vehicleData(vehicle)
|
||||
exports['ps-dispatch']:VehicleShooting(vehicle)
|
||||
Config.Timer['Shooting'] = Config.Shooting.Success
|
||||
else
|
||||
Config.Timer['Shooting'] = Config.Shooting.Fail
|
||||
end
|
||||
elseif Config.Timer['Speeding'] == 0 and playerPed == driver then
|
||||
sleep = 100
|
||||
if (GetEntitySpeed(vehicle) * 3.6) >= (120 + (math.random(30,60))) then
|
||||
Wait(400)
|
||||
if IsPedInAnyVehicle(playerPed, true) and ((GetEntitySpeed(vehicle) * 3.6) >= 90) then
|
||||
local vehicle = vehicleData(vehicle)
|
||||
exports['ps-dispatch']:SpeedingVehicle(vehicle)
|
||||
Config.Timer['Speeding'] = Config.Speeding.Success
|
||||
end
|
||||
else
|
||||
Config.Timer['Speeding'] = Config.Speeding.Fail
|
||||
end
|
||||
else
|
||||
sleep = 100
|
||||
end
|
||||
end
|
||||
else
|
||||
if Config.Timer['Shooting'] == 0 and not IsPedCurrentWeaponSilenced(playerPed) and IsPedArmed(playerPed, 4) then
|
||||
sleep = 50
|
||||
if IsPedShooting(playerPed) and not BlacklistedWeapon(playerPed) then
|
||||
exports['ps-dispatch']:Shooting()
|
||||
Config.Timer['Shooting'] = Config.Shooting.Success
|
||||
else
|
||||
Config.Timer['Shooting'] = Config.Shooting.Fail
|
||||
end
|
||||
elseif Config.Timer['Melee'] == 0 and IsPedInMeleeCombat(playerPed) and HasPedBeenDamagedByWeapon(GetMeleeTargetForPed(playerPed), 0, 1) then
|
||||
sleep = 50
|
||||
exports['ps-dispatch']:Fight()
|
||||
Config.Timer['Melee'] = Config.Melee.Success
|
||||
else sleep = 100 end
|
||||
end
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
333
resources/[ps]/ps-dispatch/client/cl_main.lua
Normal file
333
resources/[ps]/ps-dispatch/client/cl_main.lua
Normal file
@ -0,0 +1,333 @@
|
||||
PlayerData = {}
|
||||
PlayerJob = {}
|
||||
isLoggedIn = true
|
||||
QBCore = exports['qb-core']:GetCoreObject()
|
||||
local blips = {}
|
||||
|
||||
-- core related
|
||||
|
||||
AddEventHandler('onResourceStart', function(resourceName)
|
||||
if GetCurrentResourceName() == resourceName then
|
||||
isLoggedIn = true
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
PlayerJob = QBCore.Functions.GetPlayerData().job
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("QBCore:Client:OnPlayerLoaded", function()
|
||||
isLoggedIn = true
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
PlayerJob = QBCore.Functions.GetPlayerData().job
|
||||
end)
|
||||
|
||||
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
|
||||
PlayerData = {}
|
||||
isLoggedIn = false
|
||||
currentCallSign = ""
|
||||
-- currentVehicle, inVehicle, currentlyArmed, currentWeapon = nil, false, false, `WEAPON_UNARMED`
|
||||
-- removeHuntingZones()
|
||||
end)
|
||||
|
||||
RegisterNetEvent("QBCore:Client:OnJobUpdate", function(JobInfo)
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
PlayerJob = JobInfo
|
||||
end)
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function _U(entry)
|
||||
return Locales[Config.Locale][entry]
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function getSpeed() return speedlimit end
|
||||
function getStreet() return currentStreetName end
|
||||
function getStreetandZone(coords)
|
||||
local zone = GetLabelText(GetNameOfZone(coords.x, coords.y, coords.z))
|
||||
local currentStreetHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z)
|
||||
currentStreetName = GetStreetNameFromHashKey(currentStreetHash)
|
||||
playerStreetsLocation = currentStreetName .. ", " .. zone
|
||||
return playerStreetsLocation
|
||||
end
|
||||
|
||||
function refreshPlayerWhitelisted()
|
||||
if not PlayerData then return false end
|
||||
if not PlayerData.job then return false end
|
||||
if Config.Debug then return true end
|
||||
for k,v in ipairs({'police'}) do
|
||||
if v == PlayerData.job.name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function BlacklistedWeapon(playerPed)
|
||||
for i = 1, #Config.WeaponBlacklist do
|
||||
local weaponHash = GetHashKey(Config.WeaponBlacklist[i])
|
||||
if GetSelectedPedWeapon(playerPed) == weaponHash then
|
||||
return true -- Is a blacklisted weapon
|
||||
end
|
||||
end
|
||||
return false -- Is not a blacklisted weapon
|
||||
end
|
||||
|
||||
function GetAllPeds()
|
||||
local getPeds = {}
|
||||
local findHandle, foundPed = FindFirstPed()
|
||||
local continueFind = (foundPed and true or false)
|
||||
local count = 0
|
||||
while continueFind do
|
||||
local pedCoords = GetEntityCoords(foundPed)
|
||||
if GetPedType(foundPed) ~= 28 and not IsEntityDead(foundPed) and not IsPedAPlayer(foundPed) and #(playerCoords - pedCoords) < 80.0 then
|
||||
getPeds[#getPeds + 1] = foundPed
|
||||
count = count + 1
|
||||
end
|
||||
continueFind, foundPed = FindNextPed(findHandle)
|
||||
end
|
||||
EndFindPed(findHandle)
|
||||
return count
|
||||
end
|
||||
|
||||
function zoneChance(type, zoneMod, street)
|
||||
if Config.DebugChance then return true end
|
||||
if not street then street = currentStreetName end
|
||||
playerCoords = GetEntityCoords(PlayerPedId())
|
||||
local zone, sendit = GetLabelText(GetNameOfZone(playerCoords.x, playerCoords.y, playerCoords.z)), false
|
||||
if not nearbyPeds then
|
||||
nearbyPeds = GetAllPeds()
|
||||
elseif nearbyPeds < 1 then if Config.Debug then print(('^1[%s] Nobody is nearby to send a report^7'):format(type)) end
|
||||
return false
|
||||
end
|
||||
if zoneMod == nil then zoneMod = 1 end
|
||||
zoneMod = (math.ceil(zoneMod+0.5))
|
||||
local hour = GetClockHours()
|
||||
if hour >= 21 or hour <= 4 then
|
||||
zoneMod = zoneMod * 1.6
|
||||
zoneMod = math.ceil(zoneMod+0.5)
|
||||
end
|
||||
zoneMod = zoneMod / (nearbyPeds / 3)
|
||||
zoneMod = (math.ceil(zoneMod+0.5))
|
||||
local sum = math.random(1, zoneMod)
|
||||
local chance = string.format('%.2f',(1 / zoneMod) * 100)..'%'
|
||||
|
||||
if sum > 1 then
|
||||
if Config.Debug then print(('^1[%s] %s (%s) - %s nearby peds^7'):format(type, zone, chance, nearbyPeds)) end
|
||||
sendit = false
|
||||
else
|
||||
if Config.Debug then print(('^2[%s] %s (%s) - %s nearby peds^7'):format(type, zone, chance, nearbyPeds)) end
|
||||
sendit = true
|
||||
end
|
||||
print(('^1[%s] %s (%s) - %s nearby peds^7'):format(type, zone, chance, nearbyPeds))
|
||||
return sendit
|
||||
end
|
||||
|
||||
function vehicleData(vehicle)
|
||||
local vData = {}
|
||||
local vehicleClass = GetVehicleClass(vehicle)
|
||||
local vClass = {[0] = _U('compact'), [1] = _U('sedan'), [2] = _U('suv'), [3] = _U('coupe'), [4] = _U('muscle'), [5] = _U('sports_classic'), [6] = _U('sports'), [7] = _U('super'), [8] = _U('motorcycle'), [9] = _U('offroad'), [10] = _U('industrial'), [11] = _U('utility'), [12] = _U('van'), [17] = _U('service'), [19] = _U('military'), [20] = _U('truck')}
|
||||
local vehClass = vClass[vehicleClass]
|
||||
local vehicleName = GetLabelText(GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)))
|
||||
local vehicleColour1, vehicleColour2 = GetVehicleColours(vehicle)
|
||||
if vehicleColour1 then
|
||||
if Config.Colours[tostring(vehicleColour2)] and Config.Colours[tostring(vehicleColour1)] then
|
||||
vehicleColour = Config.Colours[tostring(vehicleColour2)] .. " on " .. Config.Colours[tostring(vehicleColour1)]
|
||||
elseif Config.Colours[tostring(vehicleColour1)] then
|
||||
vehicleColour = Config.Colours[tostring(vehicleColour1)]
|
||||
elseif Config.Colours[tostring(vehicleColour2)] then
|
||||
vehicleColour = Config.Colours[tostring(vehicleColour2)]
|
||||
else
|
||||
vehicleColour = "Ukendt"
|
||||
end
|
||||
end
|
||||
local plate = GetVehicleNumberPlateText(vehicle)
|
||||
local doorCount = 0
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_pside_f') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_pside_r') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_dside_f') ~= -1 then doorCount = doorCount + 1 end
|
||||
if GetEntityBoneIndexByName(vehicle, 'door_dside_r') ~= -1 then doorCount = doorCount + 1 end
|
||||
if doorCount == 2 then doorCount = _U('two_door') elseif doorCount == 3 then doorCount = _U('three_door') elseif doorCount == 4 then doorCount = _U('four_door') else doorCount = '' end
|
||||
vData.class, vData.name, vData.colour, vData.doors, vData.plate, vData.id = vehClass, vehicleName, vehicleColour, doorCount, plate, NetworkGetNetworkIdFromEntity(vehicle)
|
||||
return vData
|
||||
end
|
||||
|
||||
function GetPedGender()
|
||||
local gender = "Mand"
|
||||
if QBCore.Functions.GetPlayerData().charinfo.gender == 1 then gender = "Kvinde" end
|
||||
return gender
|
||||
end
|
||||
|
||||
function getCardinalDirectionFromHeading()
|
||||
local heading = GetEntityHeading(PlayerPedId())
|
||||
if heading >= 315 or heading < 45 then return "Nordgående"
|
||||
elseif heading >= 45 and heading < 135 then return "Vestgående"
|
||||
elseif heading >=135 and heading < 225 then return "Sydgående"
|
||||
elseif heading >= 225 and heading < 315 then return "Østgående" end
|
||||
end
|
||||
|
||||
function IsPoliceJob(job)
|
||||
for k, v in pairs(Config.PoliceJob) do
|
||||
if job == v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function IsValidJob(jobList)
|
||||
for k, v in pairs(jobList) do
|
||||
if v == PlayerJob.name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function CheckOnDuty()
|
||||
if Config.OnDutyOnly then
|
||||
return PlayerJob.onduty
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Dispatch Itself
|
||||
|
||||
local disableNotis, disableNotifSounds = false, false
|
||||
|
||||
RegisterNetEvent('dispatch:manageNotifs', function(sentSetting)
|
||||
local wantedSetting = tostring(sentSetting)
|
||||
if wantedSetting == "on" then
|
||||
disableNotis = false
|
||||
disableNotifSounds = false
|
||||
QBCore.Functions.Notify("Dispatch slået til", "success")
|
||||
elseif wantedSetting == "off" then
|
||||
disableNotis = true
|
||||
disableNotifSounds = true
|
||||
QBCore.Functions.Notify("Dispatch slået fra", "success")
|
||||
elseif wantedSetting == "mute" then
|
||||
disableNotis = false
|
||||
disableNotifSounds = true
|
||||
QBCore.Functions.Notify("Dispatch muted", "success")
|
||||
else
|
||||
QBCore.Functions.Notify('Vælg venligt "on", "off" eller "mute", for dispatch notifikationer.', "success")
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dispatch:clNotify', function(sNotificationData, sNotificationId, sender)
|
||||
if sNotificationData ~= nil and isLoggedIn then
|
||||
if IsValidJob(sNotificationData['job']) and CheckOnDuty() then
|
||||
if not disableNotis then
|
||||
if sNotificationData.origin ~= nil then
|
||||
SendNUIMessage({
|
||||
update = "newCall",
|
||||
callID = sNotificationId,
|
||||
data = sNotificationData,
|
||||
timer = 5000,
|
||||
isPolice = IsPoliceJob(PlayerJob.name)
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:AddCallBlip", function(coords, data, blipId)
|
||||
if IsValidJob(data.recipientList) and CheckOnDuty() then
|
||||
PlaySound(-1, data.sound, data.sound2, 0, 0, 1)
|
||||
TriggerServerEvent("InteractSound_SV:PlayOnSource", data.sound, 0.25) -- For Custom Sounds
|
||||
CreateThread(function()
|
||||
local alpha = 255
|
||||
local blip = nil
|
||||
local radius = nil
|
||||
local radiusAlpha = 128
|
||||
local sprite, colour, scale = 161, 84, 0.5
|
||||
local randomoffset = math.random(1,100)
|
||||
if data.blipSprite then sprite = data.blipSprite end
|
||||
if data.blipColour then colour = data.blipColour end
|
||||
if data.blipScale then scale = data.blipScale end
|
||||
if data.radius then radius = data.radius end
|
||||
if data.offset == "true" then
|
||||
if randomoffset <= 25 then
|
||||
radius = AddBlipForRadius(coords.x + math.random(Config.MinOffset, Config.MaxOffset), coords.y + math.random(Config.MinOffset, Config.MaxOffset), coords.z, data.radius)
|
||||
blip = AddBlipForCoord(coords.x + math.random(Config.MinOffset, Config.MaxOffset), coords.y + math.random(Config.MinOffset, Config.MaxOffset), coords.z)
|
||||
blips[blipId] = blip
|
||||
elseif randomoffset >= 26 and randomoffset <= 50 then
|
||||
radius = AddBlipForRadius(coords.x - math.random(Config.MinOffset, Config.MaxOffset), coords.y + math.random(Config.MinOffset, Config.MaxOffset), coords.z, data.radius)
|
||||
blip = AddBlipForCoord(coords.x - math.random(Config.MinOffset, Config.MaxOffset), coords.y + math.random(Config.MinOffset, Config.MaxOffset), coords.z)
|
||||
blips[blipId] = blip
|
||||
elseif randomoffset >= 51 and randomoffset <= 74 then
|
||||
radius = AddBlipForRadius(coords.x - math.random(Config.MinOffset, Config.MaxOffset), coords.y - math.random(Config.MinOffset, Config.MaxOffset), coords.z, data.radius)
|
||||
blip = AddBlipForCoord(coords.x - math.random(Config.MinOffset, Config.MaxOffset), coords.y - math.random(Config.MinOffset, Config.MaxOffset), coords.z)
|
||||
blips[blipId] = blip
|
||||
elseif randomoffset >= 75 and randomoffset <= 100 then
|
||||
radius = AddBlipForRadius(coords.x + math.random(Config.MinOffset, Config.MaxOffset), coords.y - math.random(Config.MinOffset, Config.MaxOffset), coords.z, data.radius)
|
||||
blip = AddBlipForCoord(coords.x + math.random(Config.MinOffset, Config.MaxOffset), coords.y - math.random(Config.MinOffset, Config.MaxOffset), coords.z)
|
||||
blips[blipId] = blip
|
||||
end
|
||||
elseif data.offset == "false" then
|
||||
radius = AddBlipForRadius(coords.x, coords.y, coords.z, data.radius)
|
||||
blip = AddBlipForCoord(coords.x, coords.y, coords.z)
|
||||
blips[blipId] = blip
|
||||
end
|
||||
if data.blipflash == "true" then
|
||||
SetBlipFlashes(blip, true)
|
||||
elseif data.blipflash == "false" then
|
||||
SetBlipFlashes(blip, false)
|
||||
end
|
||||
SetBlipSprite(blip, sprite)
|
||||
SetBlipHighDetail(blip, true)
|
||||
SetBlipScale(blip, scale)
|
||||
SetBlipColour(blip, colour)
|
||||
SetBlipAlpha(blip, alpha)
|
||||
SetBlipAsShortRange(blip, false)
|
||||
SetBlipCategory(blip, 2)
|
||||
SetBlipColour(radius, colour)
|
||||
SetBlipAlpha(radius, radiusAlpha)
|
||||
BeginTextCommandSetBlipName('STRING')
|
||||
AddTextComponentString(data.displayCode..' - '..data.description)
|
||||
EndTextCommandSetBlipName(blip)
|
||||
while radiusAlpha ~= 0 do
|
||||
Wait(data.blipLength * 1000)
|
||||
radiusAlpha = radiusAlpha - 1
|
||||
SetBlipAlpha(radius, radiusAlpha)
|
||||
if radiusAlpha == 0 then
|
||||
RemoveBlip(radius)
|
||||
RemoveBlip(blip)
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('dispatch:getCallResponse', function(message)
|
||||
SendNUIMessage({
|
||||
update = "newCall",
|
||||
callID = math.random(1000, 9999),
|
||||
data = {
|
||||
dispatchCode = 'RSP',
|
||||
priority = 1,
|
||||
dispatchMessage = "Opkald svar",
|
||||
information = message
|
||||
},
|
||||
timer = 10000,
|
||||
isPolice = true
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:Explosion", function(data)
|
||||
exports["ps-dispatch"]:Explosion()
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:removeCallBlip", function(blipId)
|
||||
RemoveBlip(blips[blipId])
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-dispatch:client:clearAllBlips", function()
|
||||
for k, v in pairs(blips) do
|
||||
RemoveBlip(v)
|
||||
end
|
||||
QBCore.Functions.Notify('Alle dispatch-blips er fjernet', "success")
|
||||
end)
|
244
resources/[ps]/ps-dispatch/config.lua
Normal file
244
resources/[ps]/ps-dispatch/config.lua
Normal file
@ -0,0 +1,244 @@
|
||||
Config = {}
|
||||
|
||||
Config.Enable = {}
|
||||
Config.Timer = {}
|
||||
|
||||
Config.PoliceJob = { "police" }
|
||||
|
||||
-- Enable if you only want to send alerts to onDuty officers
|
||||
Config.OnDutyOnly = true
|
||||
|
||||
Config.PoliceAndAmbulance = { "police", "ambulance" }
|
||||
Config.PhoneModel = 'prop_npc_phone_02'
|
||||
|
||||
-- sets report chance to 100%
|
||||
Config.DebugChance = true
|
||||
|
||||
-- Explosion Alert Types (Gas Pumps by default)
|
||||
-- Ex. Config.ExplosionTypes = {1, 2, 3, 4, 5}
|
||||
Config.ExplosionTypes = {9}
|
||||
|
||||
-- enable default alerts
|
||||
Config.Enable.Speeding = true
|
||||
Config.Enable.Shooting = true
|
||||
Config.Enable.Autotheft = true
|
||||
Config.Enable.Melee = true
|
||||
Config.Enable.PlayerDowned = true
|
||||
---------------------------------------------------------------
|
||||
Config.Locale = 'da'
|
||||
|
||||
-- enable alerts when cops break the law and print to console
|
||||
Config.Debug = false
|
||||
|
||||
-- changes the min and max offset for the radius
|
||||
Config.MinOffset = 1
|
||||
Config.MaxOffset = 120
|
||||
---------------------------------------------------------------
|
||||
|
||||
Citizen.CreateThread(function()
|
||||
-- if not GetPlayerPed(-1) then return end
|
||||
-- while not firstname do
|
||||
-- Citizen.Wait(10)
|
||||
-- end
|
||||
|
||||
-- if notLoaded then
|
||||
for k, v in pairs(Config.Enable) do
|
||||
if Config.Enable[k] ~= false then
|
||||
Config[k] = {}
|
||||
Config.Timer[k] = 0 -- Default to 0 seconds
|
||||
Config[k].Success = 300 -- Default to 30 seconds
|
||||
Config[k].Fail = 20 -- Default to 2 seconds
|
||||
end
|
||||
end
|
||||
-- If you want to set specific timers, do it here
|
||||
if Config.Shooting then
|
||||
Config.Shooting.Success = 100 -- 10 seconds
|
||||
Config.Shooting.Fail = 0 -- 0 seconds
|
||||
end
|
||||
|
||||
-- notLoaded = nil
|
||||
-- end
|
||||
|
||||
Config.WeaponBlacklist = {
|
||||
'WEAPON_GRENADE',
|
||||
'WEAPON_BZGAS',
|
||||
'WEAPON_MOLOTOV',
|
||||
'WEAPON_STICKYBOMB',
|
||||
'WEAPON_PROXMINE',
|
||||
'WEAPON_SNOWBALL',
|
||||
'WEAPON_PIPEBOMB',
|
||||
'WEAPON_BALL',
|
||||
'WEAPON_SMOKEGRENADE',
|
||||
'WEAPON_FLARE',
|
||||
'WEAPON_PETROLCAN',
|
||||
'WEAPON_FIREEXTINGUISHER',
|
||||
'WEAPON_HAZARDCAN',
|
||||
'WEAPON_RAYCARBINE',
|
||||
'WEAPON_STUNGUN'
|
||||
}
|
||||
|
||||
Config.Colours = {
|
||||
['0'] = "Metallic Black",
|
||||
['1'] = "Metallic Graphite Black",
|
||||
['2'] = "Metallic Black Steel",
|
||||
['3'] = "Metallic Dark Silver",
|
||||
['4'] = "Metallic Silver",
|
||||
['5'] = "Metallic Blue Silver",
|
||||
['6'] = "Metallic Steel Gray",
|
||||
['7'] = "Metallic Shadow Silver",
|
||||
['8'] = "Metallic Stone Silver",
|
||||
['9'] = "Metallic Midnight Silver",
|
||||
['10'] = "Metallic Gun Metal",
|
||||
['11'] = "Metallic Anthracite Grey",
|
||||
['12'] = "Matte Black",
|
||||
['13'] = "Matte Gray",
|
||||
['14'] = "Matte Light Grey",
|
||||
['15'] = "Util Black",
|
||||
['16'] = "Util Black Poly",
|
||||
['17'] = "Util Dark silver",
|
||||
['18'] = "Util Silver",
|
||||
['19'] = "Util Gun Metal",
|
||||
['20'] = "Util Shadow Silver",
|
||||
['21'] = "Worn Black",
|
||||
['22'] = "Worn Graphite",
|
||||
['23'] = "Worn Silver Grey",
|
||||
['24'] = "Worn Silver",
|
||||
['25'] = "Worn Blue Silver",
|
||||
['26'] = "Worn Shadow Silver",
|
||||
['27'] = "Metallic Red",
|
||||
['28'] = "Metallic Torino Red",
|
||||
['29'] = "Metallic Formula Red",
|
||||
['30'] = "Metallic Blaze Red",
|
||||
['31'] = "Metallic Graceful Red",
|
||||
['32'] = "Metallic Garnet Red",
|
||||
['33'] = "Metallic Desert Red",
|
||||
['34'] = "Metallic Cabernet Red",
|
||||
['35'] = "Metallic Candy Red",
|
||||
['36'] = "Metallic Sunrise Orange",
|
||||
['37'] = "Metallic Classic Gold",
|
||||
['38'] = "Metallic Orange",
|
||||
['39'] = "Matte Red",
|
||||
['40'] = "Matte Dark Red",
|
||||
['41'] = "Matte Orange",
|
||||
['42'] = "Matte Yellow",
|
||||
['43'] = "Util Red",
|
||||
['44'] = "Util Bright Red",
|
||||
['45'] = "Util Garnet Red",
|
||||
['46'] = "Worn Red",
|
||||
['47'] = "Worn Golden Red",
|
||||
['48'] = "Worn Dark Red",
|
||||
['49'] = "Metallic Dark Green",
|
||||
['50'] = "Metallic Racing Green",
|
||||
['51'] = "Metallic Sea Green",
|
||||
['52'] = "Metallic Olive Green",
|
||||
['53'] = "Metallic Green",
|
||||
['54'] = "Metallic Gasoline Blue Green",
|
||||
['55'] = "Matte Lime Green",
|
||||
['56'] = "Util Dark Green",
|
||||
['57'] = "Util Green",
|
||||
['58'] = "Worn Dark Green",
|
||||
['59'] = "Worn Green",
|
||||
['60'] = "Worn Sea Wash",
|
||||
['61'] = "Metallic Midnight Blue",
|
||||
['62'] = "Metallic Dark Blue",
|
||||
['63'] = "Metallic Saxony Blue",
|
||||
['64'] = "Metallic Blue",
|
||||
['65'] = "Metallic Mariner Blue",
|
||||
['66'] = "Metallic Harbor Blue",
|
||||
['67'] = "Metallic Diamond Blue",
|
||||
['68'] = "Metallic Surf Blue",
|
||||
['69'] = "Metallic Nautical Blue",
|
||||
['70'] = "Metallic Bright Blue",
|
||||
['71'] = "Metallic Purple Blue",
|
||||
['72'] = "Metallic Spinnaker Blue",
|
||||
['73'] = "Metallic Ultra Blue",
|
||||
['74'] = "Metallic Bright Blue",
|
||||
['75'] = "Util Dark Blue",
|
||||
['76'] = "Util Midnight Blue",
|
||||
['77'] = "Util Blue",
|
||||
['78'] = "Util Sea Foam Blue",
|
||||
['79'] = "Uil Lightning blue",
|
||||
['80'] = "Util Maui Blue Poly",
|
||||
['81'] = "Util Bright Blue",
|
||||
['82'] = "Matte Dark Blue",
|
||||
['83'] = "Matte Blue",
|
||||
['84'] = "Matte Midnight Blue",
|
||||
['85'] = "Worn Dark blue",
|
||||
['86'] = "Worn Blue",
|
||||
['87'] = "Worn Light blue",
|
||||
['88'] = "Metallic Taxi Yellow",
|
||||
['89'] = "Metallic Race Yellow",
|
||||
['90'] = "Metallic Bronze",
|
||||
['91'] = "Metallic Yellow Bird",
|
||||
['92'] = "Metallic Lime",
|
||||
['93'] = "Metallic Champagne",
|
||||
['94'] = "Metallic Pueblo Beige",
|
||||
['95'] = "Metallic Dark Ivory",
|
||||
['96'] = "Metallic Choco Brown",
|
||||
['97'] = "Metallic Golden Brown",
|
||||
['98'] = "Metallic Light Brown",
|
||||
['99'] = "Metallic Straw Beige",
|
||||
['100'] = "Metallic Moss Brown",
|
||||
['101'] = "Metallic Biston Brown",
|
||||
['102'] = "Metallic Beechwood",
|
||||
['103'] = "Metallic Dark Beechwood",
|
||||
['104'] = "Metallic Choco Orange",
|
||||
['105'] = "Metallic Beach Sand",
|
||||
['106'] = "Metallic Sun Bleeched Sand",
|
||||
['107'] = "Metallic Cream",
|
||||
['108'] = "Util Brown",
|
||||
['109'] = "Util Medium Brown",
|
||||
['110'] = "Util Light Brown",
|
||||
['111'] = "Metallic White",
|
||||
['112'] = "Metallic Frost White",
|
||||
['113'] = "Worn Honey Beige",
|
||||
['114'] = "Worn Brown",
|
||||
['115'] = "Worn Dark Brown",
|
||||
['116'] = "Worn straw beige",
|
||||
['117'] = "Brushed Steel",
|
||||
['118'] = "Brushed Black Steel",
|
||||
['119'] = "Brushed Aluminium",
|
||||
['120'] = "Chrome",
|
||||
['121'] = "Worn Off White",
|
||||
['122'] = "Util Off White",
|
||||
['123'] = "Worn Orange",
|
||||
['124'] = "Worn Light Orange",
|
||||
['125'] = "Metallic Securicor Green",
|
||||
['126'] = "Worn Taxi Yellow",
|
||||
['127'] = "Police Car Blue",
|
||||
['128'] = "Matte Green",
|
||||
['129'] = "Matte Brown",
|
||||
['130'] = "Worn Orange",
|
||||
['131'] = "Matte White",
|
||||
['132'] = "Worn White",
|
||||
['133'] = "Worn Olive Army Green",
|
||||
['134'] = "Pure White",
|
||||
['135'] = "Hot Pink",
|
||||
['136'] = "Salmon pink",
|
||||
['137'] = "Metallic Vermillion Pink",
|
||||
['138'] = "Orange",
|
||||
['139'] = "Green",
|
||||
['140'] = "Blue",
|
||||
['141'] = "Mettalic Black Blue",
|
||||
['142'] = "Metallic Black Purple",
|
||||
['143'] = "Metallic Black Red",
|
||||
['144'] = "hunter green",
|
||||
['145'] = "Metallic Purple",
|
||||
['146'] = "Metallic Dark Blue",
|
||||
['147'] = "Black",
|
||||
['148'] = "Matte Purple",
|
||||
['149'] = "Matte Dark Purple",
|
||||
['150'] = "Metallic Lava Red",
|
||||
['151'] = "Matte Forest Green",
|
||||
['152'] = "Matte Olive Drab",
|
||||
['153'] = "Matte Desert Brown",
|
||||
['154'] = "Matte Desert Tan",
|
||||
['155'] = "Matte Foilage Green",
|
||||
['156'] = "Default Alloy Color",
|
||||
['157'] = "Epsilon Blue",
|
||||
['158'] = "Pure Gold",
|
||||
['159'] = "Brushed Gold",
|
||||
['160'] = "MP100"
|
||||
}
|
||||
|
||||
end)
|
30
resources/[ps]/ps-dispatch/fxmanifest.lua
Normal file
30
resources/[ps]/ps-dispatch/fxmanifest.lua
Normal file
@ -0,0 +1,30 @@
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
version '0.0'
|
||||
description 'https://github.com/Project-Sloth/ps-dispatch'
|
||||
|
||||
shared_scripts {
|
||||
'config.lua',
|
||||
'locales/locales.lua',
|
||||
}
|
||||
|
||||
client_scripts{
|
||||
'client/cl_main.lua',
|
||||
'client/cl_events.lua',
|
||||
'client/cl_extraalerts.lua',
|
||||
'client/cl_commands.lua',
|
||||
'client/cl_loops.lua',
|
||||
}
|
||||
server_script {
|
||||
'server/sv_dispatchcodes.lua',
|
||||
'server/sv_main.lua'
|
||||
}
|
||||
|
||||
ui_page 'ui/index.html'
|
||||
|
||||
files {
|
||||
'ui/index.html',
|
||||
'ui/app.js',
|
||||
'ui/style.css',
|
||||
}
|
63
resources/[ps]/ps-dispatch/locales/locales.lua
Normal file
63
resources/[ps]/ps-dispatch/locales/locales.lua
Normal file
@ -0,0 +1,63 @@
|
||||
Locales = {
|
||||
['da'] = {
|
||||
['unknown_caller'] = "Opkalder Ukendt",
|
||||
['caller_unknown'] = "Ukendt",
|
||||
['caller_local'] = "Lokal Borger",
|
||||
['call_from'] = "Opkald fra ",
|
||||
['two_door'] = "To-dørs ",
|
||||
['three_door'] = "Tre-dørs ",
|
||||
['four_door'] = "Fire-dørs ",
|
||||
['compact'] = "Compact",
|
||||
['sedan'] = "Sedan",
|
||||
['suv'] = "SUV",
|
||||
['coupe'] = "Coupe",
|
||||
['muscle'] = "Muskelbil",
|
||||
['sports_classic'] = "Klasikker",
|
||||
['sports'] = "Sportsbil",
|
||||
['super'] = "Superbil",
|
||||
['motorcycle'] = "Motorcykel",
|
||||
['offroad'] = "Off-road",
|
||||
['industrial'] = "Industriel",
|
||||
['utility'] = "Utility køretøj",
|
||||
['van'] = "Van",
|
||||
['service'] = "Service køretøj",
|
||||
['military'] = "Militær køretøj",
|
||||
['truck'] = "Lastbil",
|
||||
|
||||
-- Meter Robbery
|
||||
['meterrobbery'] = "Meter Robbery",
|
||||
-- ATM Robbery
|
||||
['atmrobbery'] = "Hæveautomat røveri",
|
||||
|
||||
-- DISPATCH MESSAGES
|
||||
['vehicleshots'] = "Skud affyret fra køretøj",
|
||||
['melee'] = "Slås kamp i gang",
|
||||
['shooting'] = "Skyderi i gang",
|
||||
['driveby'] = "Drive-by skyderi",
|
||||
['speeding'] = "Uansvarlig kørsel",
|
||||
['autotheft'] = "Tyveri af et køretøj",
|
||||
['officerdown'] = "KOLLEGA SKADET",
|
||||
['persondown'] = "Personen er såret",
|
||||
['storerobbery'] = "Røveri i butik",
|
||||
['fleecabank'] = "Fleeca Bank røveri",
|
||||
['paletobank'] = "Paleto Bank røveri",
|
||||
['pacificbank'] = "Pacific Bank røveri",
|
||||
['prisonbreak'] = "Fangeflugt i gang",
|
||||
['vangelico'] = "Vangelico røveri",
|
||||
['houserobbery'] = "Hus røveri",
|
||||
['drugsell'] = "Mistænkelig handel",
|
||||
['carjacking'] = "Biltyveri",
|
||||
['vehicletheft'] = "Køretøjstyveri",
|
||||
['emsdown'] = "EMS nede",
|
||||
['artgalleryrobbery'] = "Kunstgalleri røveri",
|
||||
['humanerobbery'] = "Humane Labs røveri",
|
||||
['trainrobbery'] = "Tog røveri",
|
||||
['vanrobbery'] = "Sikkerheds Van røveri",
|
||||
['underground'] = "Bunker røveri",
|
||||
['drugboatrobbery'] = "Mistænkelig båd",
|
||||
['unionrobbery'] = "Union Depository røveri",
|
||||
['carboosting'] = "Bil Boosting i gang",
|
||||
['yachtheist'] = "Yacht Røveri i gang",
|
||||
['susactivity'] = "Mistænkelig aktivitet",
|
||||
}
|
||||
}
|
90
resources/[ps]/ps-dispatch/server/sv_dispatchcodes.lua
Normal file
90
resources/[ps]/ps-dispatch/server/sv_dispatchcodes.lua
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
--[[
|
||||
["vehicleshots"] -> dispatchcodename that you pass with the event of AlertGunShot
|
||||
displayCode -> Code to be displayed on the blip message
|
||||
description -> Description of the blip message
|
||||
radius -> to draw a circle with radius around blip ( the number need to have a .0 behind it, for example 150.0 or 75.0 )
|
||||
-> if u want to have the radius without the blip just make the blipScale = 0
|
||||
-> if u want to have the radius centered, disable the offset
|
||||
recipientList -> list of job names that can see the blip
|
||||
blipSprite -> blip sprite
|
||||
blipColour -> blip colour
|
||||
blipScale -> blip scale
|
||||
blipLength -> time in seconds at which the blip will fade down, lower the time, faster it will fade. Cannot be 0
|
||||
offset -> enable or disable the offset for the radius ( radius on 0 and offset on true does nothing )
|
||||
blipflash -> enable or disable the flashing blip
|
||||
]]--
|
||||
|
||||
dispatchCodes = {
|
||||
|
||||
["vehicleshots"] = {displayCode = '10-13', description = "Skud affyret fra køretøj", radius = 0, recipientList = {'police'}, blipSprite = 119, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["shooting"] = {displayCode = '10-13', description ="Skud affyret", radius = 0, recipientList = {'police'}, blipSprite = 110, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["speeding"] = {displayCode = '10-13', description = "Overtrædelse af fartgrænse", radius = 0, recipientList = {'police'}, blipSprite = 326, blipColour = 84, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["fight"] = {displayCode = '10-10', description = "Igangværende slåskamp", radius = 0, recipientList = {'police'}, blipSprite = 685, blipColour = 69, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["civdown"] = {displayCode = '10-69', description = "Civil nede", radius = 0, recipientList = {'ambulance'}, blipSprite = 126, blipColour = 3, blipScale = 1.5, blipLength = 2, sound = "dispatch", offset = "false", blipflash = "false"},
|
||||
|
||||
["civdead"] = {displayCode = '10-69', description = "Civil afgået ved døden", radius = 0, recipientList = {'ambulance'}, blipSprite = 126, blipColour = 3, blipScale = 1.5, blipLength = 2, sound = "dispatch", offset = "false", blipflash = "false"},
|
||||
|
||||
["114call"] = {displayCode = '114', description = "114 opkald", radius = 0, recipientList = {'police', 'ambulance'}, blipSprite = 480, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["112call"] = {displayCode = '112', description = "112 opkald", radius = 0, recipientList = {'police', 'ambulance'}, blipSprite = 480, blipColour = 3, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["officerdown"] = {displayCode = '10-99', description = "BETJENT NEDE", radius = 15.0, recipientList = {'police', 'ambulance'}, blipSprite = 526, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "panicbutton", offset = "false", blipflash = "true"},
|
||||
|
||||
["emsdown"] = {displayCode = '10-99', description = "EMS NEDE", radius = 15.0, recipientList = {'police', 'ambulance'}, blipSprite = 526, blipColour = 3, blipScale = 1.5, blipLength = 2, sound = "panicbutton", offset = "false", blipflash = "true"},
|
||||
|
||||
["storerobbery"] = {displayCode = '10-90', description = "Butiksrøveri igang", radius = 0, recipientList = {'police'}, blipSprite = 52, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["bankrobbery"] = {displayCode = '10-90', description = "Fleeca Bank røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 500, blipColour = 2, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["paletobankrobbery"] = {displayCode = '10-90', description = "Paleto Bank røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 500, blipColour = 12, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["pacificbankrobbery"] = {displayCode = '10-90', description = "Pacific Bank røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 500, blipColour = 5, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["prisonbreak"] = {displayCode = '10-90', description = "Fængselsflugt igang", radius = 0, recipientList = {'police'}, blipSprite = 189, blipColour = 59, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["vangelicorobbery"] = {displayCode = '10-90', description = "Vangelico røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 434, blipColour = 5, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["houserobbery"] = {displayCode = '10-90', description = "House røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 40, blipColour = 5, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["suspicioushandoff"] = {displayCode = '10-60', description = "Mistænkelig levering", radius = 120.0, recipientList = {'police'}, blipSprite = 469, blipColour = 52, blipScale = 0, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "true", blipflash = "false"},
|
||||
|
||||
["yachtheist"] = {displayCode = '10-65', description = "Yacht Heist igang", radius = 0, recipientList = {'police'}, blipSprite = 455, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["vehicletheft"] = {displayCode = '10-35', description = "Car Theft igang", radius = 0, recipientList = {'police'}, blipSprite = 595, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["signrobbery"] = {displayCode = '10-35', description = "Skiltetyveri", radius = 0, recipientList = {'police'}, blipSprite = 358, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["susactivity"] = {displayCode = '10-66', description = "Mistænkelig aktivitet", radius = 0, recipientList = {'police'}, blipSprite = 66, blipColour = 37, blipScale = 0.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
-- Meter Robbery
|
||||
["meterrobbery"] = {displayCode = '10-90', description = "Parkeringsmeter røveri igang", radius = 0, recipientList = {'LEO', 'police'}, blipSprite = 500, clipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "FTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
-- ATM Robbery
|
||||
["atmrobbery"] = {displayCode = '10-90', description = "ATM røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 500, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
|
||||
-- Rainmad Heists
|
||||
|
||||
["artgalleryrobbery"] = {displayCode = '10-90', description = "Kunstgalleri røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 269, blipColour = 59, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["humanelabsrobbery"] = {displayCode = '10-90', description = "Humane Labs røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 499, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["trainrobbery"] = {displayCode = '10-90', description = "Togrøveri igang", radius = 0, recipientList = {'police'}, blipSprite = 667, blipColour = 78, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["vanrobbery"] = {displayCode = '10-90', description = "Pengetransport røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 67, blipColour = 59, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["undergroundrobbery"] = {displayCode = '10-90', description = "Undergrundstunnel røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 486, blipColour = 59, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["drugboatrobbery"] = {displayCode = '10-31', description = "Mistænkelig aktivitet på båd", radius = 0, recipientList = {'police'}, blipSprite = 427, blipColour = 26, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["unionrobbery"] = {displayCode = '10-90', description = "Union Depository røveri igang", radius = 0, recipientList = {'police'}, blipSprite = 500, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "robberysound", offset = "false", blipflash = "false"},
|
||||
|
||||
["carboosting"] = {displayCode = '10-50', description = "Carboosting igang", radius = 0, recipientList = {'police'}, blipSprite = 595, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["carjack"] = {displayCode = '10-35', description = "Biltyveri igang", radius = 0, recipientList = {'police'}, blipSprite = 595, blipColour = 60, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
|
||||
["explosion"] = {displayCode = '10-80', description = "Eksplosion meldt", radius = 0, recipientList = {'police'}, blipSprite = 436, blipColour = 1, blipScale = 1.5, blipLength = 2, sound = "Lose_1st", sound2 = "GTAO_FM_Events_Soundset", offset = "false", blipflash = "false"},
|
||||
}
|
133
resources/[ps]/ps-dispatch/server/sv_main.lua
Normal file
133
resources/[ps]/ps-dispatch/server/sv_main.lua
Normal file
@ -0,0 +1,133 @@
|
||||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
local calls = {}
|
||||
|
||||
function _U(entry)
|
||||
return Locales[Config.Locale][entry]
|
||||
end
|
||||
|
||||
local function IsPoliceJob(job)
|
||||
for k, v in pairs(Config.PoliceJob) do
|
||||
if job == v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function IsDispatchJob(job)
|
||||
for k, v in pairs(Config.PoliceAndAmbulance) do
|
||||
if job == v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
RegisterServerEvent("dispatch:server:notify", function(data)
|
||||
local newId = #calls + 1
|
||||
calls[newId] = data
|
||||
calls[newId]['source'] = source
|
||||
calls[newId]['callId'] = newId
|
||||
calls[newId]['units'] = {}
|
||||
calls[newId]['responses'] = {}
|
||||
calls[newId]['time'] = os.time() * 1000
|
||||
|
||||
TriggerClientEvent('dispatch:clNotify', -1, data, newId, source)
|
||||
if not data.alert then
|
||||
TriggerClientEvent("ps-dispatch:client:AddCallBlip", -1, data.origin, dispatchCodes[data.dispatchcodename], newId)
|
||||
else
|
||||
TriggerClientEvent("ps-dispatch:client:AddCallBlip", -1, data.origin, data.alert, newId)
|
||||
end
|
||||
end)
|
||||
|
||||
function GetDispatchCalls() return calls end
|
||||
exports('GetDispatchCalls', GetDispatchCalls) --
|
||||
|
||||
-- this is mdt call
|
||||
AddEventHandler("dispatch:addUnit", function(callid, player, cb)
|
||||
if calls[callid] then
|
||||
if #calls[callid]['units'] > 0 then
|
||||
for i=1, #calls[callid]['units'] do
|
||||
if calls[callid]['units'][i]['cid'] == player.cid then
|
||||
cb(#calls[callid]['units'])
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if IsPoliceJob(player.job.name) then
|
||||
calls[callid]['units'][#calls[callid]['units']+1] = { cid = player.cid, fullname = player.fullname, job = 'Police', callsign = player.callsign }
|
||||
elseif player.job.name == 'ambulance' then
|
||||
calls[callid]['units'][#calls[callid]['units']+1] = { cid = player.cid, fullname = player.fullname, job = 'EMS', callsign = player.callsign }
|
||||
end
|
||||
cb(#calls[callid]['units'])
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("dispatch:sendCallResponse", function(player, callid, message, time, cb)
|
||||
local Player = QBCore.Functions.GetPlayer(player)
|
||||
local name = Player.PlayerData.charinfo.firstname.. " " ..Player.PlayerData.charinfo.lastname
|
||||
if calls[callid] then
|
||||
calls[callid]['responses'][#calls[callid]['responses']+1] = {
|
||||
name = name,
|
||||
message = message,
|
||||
time = time
|
||||
}
|
||||
local player = calls[callid]['source']
|
||||
if GetPlayerPing(player) > 0 then
|
||||
TriggerClientEvent('dispatch:getCallResponse', player, message)
|
||||
end
|
||||
cb(true)
|
||||
else
|
||||
cb(false)
|
||||
end
|
||||
end)
|
||||
|
||||
-- this is mdt call
|
||||
AddEventHandler("dispatch:removeUnit", function(callid, player, cb)
|
||||
if calls[callid] then
|
||||
if #calls[callid]['units'] > 0 then
|
||||
for i=1, #calls[callid]['units'] do
|
||||
if calls[callid]['units'][i]['cid'] == player.cid then
|
||||
calls[callid]['units'][i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
cb(#calls[callid]['units'])
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
RegisterCommand('togglealerts', function(source, args, user)
|
||||
local source = source
|
||||
local Player = QBCore.Functions.GetPlayer(source)
|
||||
local job = Player.PlayerData.job
|
||||
if IsPoliceJob(job.name) or job.name == 'ambulance' then
|
||||
TriggerClientEvent('dispatch:manageNotifs', source, args[1])
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Explosion Handler
|
||||
local ExplosionCooldown = false
|
||||
AddEventHandler('explosionEvent', function(source, info)
|
||||
if ExplosionCooldown then return end
|
||||
|
||||
for i = 1, (#Config.ExplosionTypes) do
|
||||
if info.explosionType == Config.ExplosionTypes[i] then
|
||||
TriggerClientEvent("ps-dispatch:client:Explosion", source)
|
||||
ExplosionCooldown = true
|
||||
SetTimeout(1500, function()
|
||||
ExplosionCooldown = false
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
QBCore.Commands.Add("cdb", "Fjern alle dispatch-blips", {}, false, function(source, args)
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local job = Player.PlayerData.job.name
|
||||
if IsDispatchJob(job) then
|
||||
TriggerClientEvent('ps-dispatch:client:clearAllBlips', src)
|
||||
end
|
||||
end)
|
BIN
resources/[ps]/ps-dispatch/sounds/dispatch.ogg
Normal file
BIN
resources/[ps]/ps-dispatch/sounds/dispatch.ogg
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-dispatch/sounds/panicbutton.ogg
Normal file
BIN
resources/[ps]/ps-dispatch/sounds/panicbutton.ogg
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-dispatch/sounds/robberysound.ogg
Normal file
BIN
resources/[ps]/ps-dispatch/sounds/robberysound.ogg
Normal file
Binary file not shown.
165
resources/[ps]/ps-dispatch/ui/app.js
Normal file
165
resources/[ps]/ps-dispatch/ui/app.js
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
$(document).ready(() => {
|
||||
window.addEventListener('message', function (event) {
|
||||
let data = event.data;
|
||||
if (data.update == "newCall") {
|
||||
addNewCall(data.callID, data.timer, data.data, data.isPolice);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const MONTH_NAMES = [
|
||||
'Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni',
|
||||
'Juli', 'August', 'September', 'Oktober', 'November', 'December'
|
||||
];
|
||||
|
||||
function getFormattedDate(date, prefomattedDate = false, hideYear = false) {
|
||||
const day = date.getDate();
|
||||
const month = MONTH_NAMES[date.getMonth()];
|
||||
const year = date.getFullYear();
|
||||
const hours = date.getHours();
|
||||
let minutes = date.getMinutes();
|
||||
|
||||
if (minutes < 10) {
|
||||
minutes = `0${minutes}`;
|
||||
}
|
||||
|
||||
if (prefomattedDate) {
|
||||
return `${prefomattedDate} kl. ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
if (hideYear) {
|
||||
return `${day}. ${month} kl. ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
return `${day}. ${month} ${year}. kl. ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function timeAgo(dateParam) {
|
||||
if (!dateParam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam);
|
||||
const DAY_IN_MS = 86400000;
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today - DAY_IN_MS);
|
||||
const seconds = Math.round((today - date) / 1000);
|
||||
const minutes = Math.round(seconds / 60);
|
||||
const isToday = today.toDateString() === date.toDateString();
|
||||
const isYesterday = yesterday.toDateString() === date.toDateString();
|
||||
const isThisYear = today.getFullYear() === date.getFullYear();
|
||||
|
||||
if (seconds < 5) {
|
||||
return 'Lige nu';
|
||||
} else if (seconds < 60) {
|
||||
return `${seconds} sekunder siden`;
|
||||
} else if (seconds < 90) {
|
||||
return 'Omkring et minut siden';
|
||||
} else if (minutes < 60) {
|
||||
return `${minutes} minutter siden`;
|
||||
} else if (isToday) {
|
||||
return getFormattedDate(date, 'I dag');
|
||||
} else if (isYesterday) {
|
||||
return getFormattedDate(date, 'I går');
|
||||
} else if (isThisYear) {
|
||||
return getFormattedDate(date, false, true);
|
||||
}
|
||||
|
||||
return getFormattedDate(date);
|
||||
}
|
||||
|
||||
function addNewCall(callID, timer, info, isPolice) {
|
||||
const prio = info['priority']
|
||||
let DispatchItem;
|
||||
if (info['isDead']){
|
||||
DispatchItem = `<div class="dispatch-item ${callID} dispatch-item-${info['isDead']} animate__animated"><div class="top-info-holder"><div class="call-id">#${callID}</div><div class="call-code priority-${prio}">${info.dispatchCode}</div><div class="call-name">${info.dispatchMessage}</div></div><div class="bottom-info-holder">`
|
||||
}
|
||||
else{
|
||||
DispatchItem = `<div class="dispatch-item ${callID} dispatch-item-${isPolice} animate__animated"><div class="top-info-holder"><div class="call-id">#${callID}</div><div class="call-code priority-${prio}">${info.dispatchCode}</div><div class="call-name">${info.dispatchMessage}</div></div><div class="bottom-info-holder">`
|
||||
}
|
||||
|
||||
|
||||
// Above we are defining a default dispatch item and then we will append the data we have been sent.
|
||||
|
||||
if (info['time']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-clock" style="width: 20px;"></span>${timeAgo(info['time'])}</div>`
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (info['firstStreet']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-map-pin" style="padding-left:3px;width:16px;></span>${info['firstStreet']}</div>`
|
||||
}
|
||||
|
||||
if (info['heading']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-share" style="padding-left:0px;width:19px;></span>${info['heading']}</div>`
|
||||
}
|
||||
|
||||
if (info['callsign']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-solid fa-eye" style="margin-left:-1px;width:20px;></span>${info['callsign']}</div>`
|
||||
}
|
||||
|
||||
if (info['doorCount']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-door-open" style="margin-left:-3px;width:22px;></span>${info['doorCount']}</div>`
|
||||
}
|
||||
|
||||
if (info['weapon']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-bullseye" style="width:19px;></span>${info['weapon']}</div>`
|
||||
}
|
||||
|
||||
if (info["camId"]) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-camera" style="width:19px;></span>${info['camId']}</div>`
|
||||
}
|
||||
|
||||
if (info['gender']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-genderless" style="padding-left:3px;width:16px;></span>${info['gender']}</div>`
|
||||
}
|
||||
|
||||
if (info['model'] && info['plate']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-car" style="width:19px;></span>${info['model']}<span class="fas fa-digital-tachograph" style="margin-left: 2vh;"></span>${info['plate']}</div>`
|
||||
} else if (info['plate']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-digital-tachograph" style="margin-left:-3px;width:22px;></span>${info['plate']}</div>`
|
||||
} else if (info['model']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-car" style="width:19px;></span>${info['model']}</div>`
|
||||
}
|
||||
|
||||
if (info['firstColor']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-spray-can" style="padding-left:1px;width:19px;></span>${info['firstColor']}</div>`
|
||||
}
|
||||
if (info['automaticGunfire'] == true) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fab fa-blackberry" style="width:19px;></span>Automatic Gunfire</div>`
|
||||
}
|
||||
|
||||
if (info['name'] && info['number']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="far fa-id-badge" style="padding-left: 1px; width: 18px;></span>${info['name']}<span class="fas fa-mobile-alt" style="margin-left: 2vh;"></span>${info['number']}</div>`
|
||||
} else if (info['number']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="fas fa-mobile-alt" style="padding-left:3px;width:16px;></span>${info['number']}</div>`
|
||||
} else if (info['name']) {
|
||||
DispatchItem += `<div class="call-bottom-info"><span class="far fa-id-badge" style="padding-left: 1px; width: 18px;></span>${info['name']}</div>`
|
||||
}
|
||||
|
||||
if (info['information']) {
|
||||
DispatchItem += `<div class="line"></div><div class="call-bottom-info call-bottom-information"><span class="far fa-question-circle" style="width:19px;></span>${info['information']}</div>`
|
||||
}
|
||||
|
||||
DispatchItem += `</div></div>`
|
||||
|
||||
$(".dispatch-holder").prepend(DispatchItem)
|
||||
|
||||
var timer = 4000
|
||||
|
||||
if (prio == 1) {
|
||||
timer = 12000
|
||||
} else if (prio == 2) {
|
||||
timer = 9000
|
||||
}
|
||||
|
||||
$(`.${callID}`).addClass("animate__backInRight");
|
||||
setTimeout(() => {
|
||||
$(`.${callID}`).addClass("animate__backOutRight");
|
||||
setTimeout(() => {
|
||||
$(`.${callID}`).remove();
|
||||
}, 1000);
|
||||
}, timer || 4500);
|
||||
};
|
24
resources/[ps]/ps-dispatch/ui/index.html
Normal file
24
resources/[ps]/ps-dispatch/ui/index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>QB Dispatch</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css">
|
||||
<script src="app.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="dispatch-container">
|
||||
<div class="dispatch-holder">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
198
resources/[ps]/ps-dispatch/ui/style.css
Normal file
198
resources/[ps]/ps-dispatch/ui/style.css
Normal file
@ -0,0 +1,198 @@
|
||||
@import url('https://api.hawk.quest/fonts/SF-Pro-Rounded-Regular.woff2');
|
||||
body {
|
||||
font-family: 'SF Pro Rounded Regular';
|
||||
overflow: hidden;
|
||||
/* display: none; */
|
||||
text-shadow: 1px 1px 1px rgb(0 0 0 / 32%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.dispatch-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dispatch-holder {
|
||||
width: 48vh;
|
||||
height: 50vh;
|
||||
margin: auto;
|
||||
margin-top: 21.75vh;
|
||||
margin-right: 3vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.dispatch-item {
|
||||
/* -webkit-text-stroke: 0.1mm black; */
|
||||
/* background-image: url('https://web.archive.org/web/20231211222955/https://cdn.discordapp.com/attachments/1021510724754878484/1043206485620043777/imageedit_6_2893157692.png'); */
|
||||
background-size: 28px;
|
||||
background-repeat: no-repeat;
|
||||
/* background-position-x: right;
|
||||
background-position-y: top; */
|
||||
background-position: right 2% bottom 87%;
|
||||
/* border-radius: 5px; */
|
||||
margin: auto;
|
||||
margin-top: 5vh;
|
||||
margin-right: 1vh;
|
||||
margin-bottom: .5vh;
|
||||
background-color: #1401019c;
|
||||
border-right: .75vh solid rgba(217, 249, 255, 0.671);
|
||||
min-width: 45.5vh;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
border-radius: 1.1vh;
|
||||
}
|
||||
|
||||
.dispatch-item-true {
|
||||
background-color: #00030594;
|
||||
}
|
||||
|
||||
.dispatch-item-false {
|
||||
background-color: rgba(119, 20, 20, 0.911);
|
||||
border-right: .75vh solid rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
.dispatch-item-officer {
|
||||
background-color: rgb(97, 3, 11);
|
||||
}
|
||||
|
||||
.top-info-holder {
|
||||
scale: 0.5px;
|
||||
width: 95%;
|
||||
height: fit-content;
|
||||
margin: unset;
|
||||
margin-top: .75vh;
|
||||
margin-left: .75vh;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: white;
|
||||
font-size: 1vh;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.call-id {
|
||||
background-color: #93f167;
|
||||
padding: .35vh;
|
||||
padding-bottom: 0.4vh;
|
||||
padding-left: 1vh;
|
||||
padding-right: 1vh;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 0vh;
|
||||
margin-right: 0.4vh;
|
||||
border-radius: 1vh;
|
||||
text-align: center;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.call-code {
|
||||
background-color: #000b0e;
|
||||
padding: .35vh;
|
||||
padding-bottom: 0.4vh;
|
||||
padding-left: 1vh;
|
||||
padding-right: 1vh;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 0vh;
|
||||
margin-right: 0.5vh;
|
||||
border-radius: 1vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-name {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 0vh;
|
||||
margin-right: auto;
|
||||
border-radius: 1vh;
|
||||
text-align: left;
|
||||
font-size: 1.4vh;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.bottom-info-holder {
|
||||
-webkit-text-stroke: 0.01mm black;
|
||||
width: 95%;
|
||||
height: fit-content;
|
||||
margin: auto;
|
||||
margin-top: .75vh;
|
||||
margin-bottom: .6vh;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
font-size: 1.2vh;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.call-bottom-info {
|
||||
margin: auto;
|
||||
margin-left: 0vh;
|
||||
margin-bottom: .5vh;
|
||||
text-align: left;
|
||||
overflow: wrap;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.line {
|
||||
background-color: rgb(0, 4, 7);
|
||||
height: 0.05vh;
|
||||
width: 100%;
|
||||
margin-top: 0.1vh;
|
||||
margin-bottom: 0.1vh
|
||||
}
|
||||
|
||||
.call-bottom-information {
|
||||
margin-top: 0.5vh;
|
||||
}
|
||||
|
||||
.fas {
|
||||
margin-right: .5vh;
|
||||
}
|
||||
|
||||
.fab {
|
||||
margin-right: .5vh;
|
||||
}
|
||||
|
||||
.far {
|
||||
margin-right: .5vh;
|
||||
}
|
||||
|
||||
.fab {
|
||||
margin-right: .5vh;
|
||||
}
|
||||
|
||||
.priority-1 {
|
||||
background: linear-gradient(270deg, #b70000, #00020a);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 3s ease infinite;
|
||||
background-color: #000b0e00;
|
||||
}
|
||||
|
||||
.priority-2 {
|
||||
background-color: #ff2e2e;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
}
|
||||
}
|
BIN
resources/[ps]/ps-dispatch/ui/style.rar
Normal file
BIN
resources/[ps]/ps-dispatch/ui/style.rar
Normal file
Binary file not shown.
2
resources/[ps]/ps-housing/.gitattributes
vendored
Normal file
2
resources/[ps]/ps-housing/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
27
resources/[ps]/ps-housing/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
resources/[ps]/ps-housing/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
2
resources/[ps]/ps-housing/.gitignore
vendored
Normal file
2
resources/[ps]/ps-housing/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
.DS_Store
|
458
resources/[ps]/ps-housing/LICENSE
Normal file
458
resources/[ps]/ps-housing/LICENSE
Normal file
@ -0,0 +1,458 @@
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
|
||||
https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
This is a human-readable summary of (and not a substitute for) the license. Disclaimer.
|
||||
|
||||
You are free to:
|
||||
|
||||
Share — copy and redistribute the material in any medium or format
|
||||
Adapt — remix, transform, and build upon the material
|
||||
|
||||
The licensor cannot revoke these freedoms as long as you follow the license terms.
|
||||
|
||||
Under the following terms:
|
||||
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
||||
|
||||
NonCommercial — You may not use the material for commercial purposes.
|
||||
|
||||
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
||||
|
||||
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
|
||||
|
||||
Notices:
|
||||
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
|
||||
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||
("Public License"). To the extent this Public License may be
|
||||
interpreted as a contract, You are granted the Licensed Rights in
|
||||
consideration of Your acceptance of these terms and conditions, and the
|
||||
Licensor grants You such rights in consideration of benefits the
|
||||
Licensor receives from making the Licensed Material available under
|
||||
these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-NC-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. NonCommercial means not primarily intended for or directed towards
|
||||
commercial advantage or monetary compensation. For purposes of
|
||||
this Public License, the exchange of the Licensed Material for
|
||||
other material subject to Copyright and Similar Rights by digital
|
||||
file-sharing or similar means is NonCommercial provided there is
|
||||
no payment of monetary compensation in connection with the
|
||||
exchange.
|
||||
|
||||
l. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
m. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
n. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part, for NonCommercial purposes only; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material for
|
||||
NonCommercial purposes only.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties, including when
|
||||
the Licensed Material is used other than for NonCommercial
|
||||
purposes.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-NC-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database for NonCommercial purposes
|
||||
only;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
344
resources/[ps]/ps-housing/README.md
Normal file
344
resources/[ps]/ps-housing/README.md
Normal file
@ -0,0 +1,344 @@
|
||||
# ps-housing
|
||||
|
||||
ps-housing is a resource that opens up a world of creative possibilities for housing. Its user-friendly interface lets you decorate any location to your heart's content. The best part? Not only is it completely free, but it's also reliable and functional, unlike many other housing systems available. Dive in and start transforming spaces with ps-housing today!
|
||||
|
||||
ps-housing owes its existence to the exceptional coding expertise of [Xirvin#0985](https://github.com/ImXirvin). His application of top-tier coding practices has been instrumental in creating this script. We at Project Sloth are thrilled that he has joined our team and utilized our platform to deliver this incredible, much-anticipated resource. Our sincere appreciation goes out to [Xirvin#0985](https://github.com/ImXirvin) for his outstanding contribution!
|
||||
|
||||
# Preview [ps-housing](https://github.com/Project-Sloth/ps-housing)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Preview [ps-realtor](https://github.com/Project-Sloth/ps-realtor)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Usage
|
||||
- Players can decorate their houses and apartments with a full selection of furniture and decorations (included a wide variety of custom housing props)
|
||||
- Provides support for housing and apartments and is a full replacement for qb-apartments and qb-housing
|
||||
- When a player first spawns after enabling ps-housing, they will have to choose an apartment. Once they spawn in the stashitems from their previous qb-apartment will be migrated to their new apartment stash.
|
||||
- Allows players to purchase and list houses for sale through `ps-realtor` and the realtor job
|
||||
- Houses come with personal garages
|
||||
- Houses and apartments come with personal wardrobes and stashes
|
||||
- Players can share keys to their houses and apartments with other players
|
||||
|
||||
## Creating a new property for sale
|
||||
Players must have the realtor job to create new properties. Additionally if the realtor has a high enough grade level, they can also help players move to new apartments.
|
||||
All properties must be manually configured for sale by the realtor job, giving you full control over all aspects of properties, and bringing another avenue of roleplay to your server.
|
||||
|
||||
- Pick the location where you want to create a new property
|
||||
- Use `/housing` to open the housing menu
|
||||
- Click on create new property
|
||||
- Fill out the details of the property (name, price, description, which shell to use, etc)
|
||||
- Choose the door location (this is where the person will enter the house)
|
||||
- Ensure that you place it up against a wall, since players will use target to enter the house
|
||||
- Choose the garage location
|
||||
- This point is used both for storing vehicles, as well as the location where the vehicle will spawn when taken out of the garage
|
||||
- Realtors can edit the details of the property by clicking on the property in the housing menu
|
||||
- Players can see the properties for sale through the /housing menu as well
|
||||
|
||||
## Furnish and decorate a property
|
||||
Once inside the property, the player can furnish and decorate the property to their liking. They can also invite other players to their property, and give them access to the property. Open the furniture store by pressing `Z`.
|
||||
|
||||
This will open a furniture store complete with all of the props. Select an item from the catalog and place it into the property. You can use the placement gizmo to position the item to your liking as well as use the UI tools for fine tune control over the placement. Once you are happy with the positioning, make sure you press `Add to Cart` before moving on. Continue to add as many items as you want to your cart. Once you are done, go to the `Checkout` and purchase the items.
|
||||
|
||||
> Note: The place on ground button sometimes does not work properly depending on where the native detects the ground to be.
|
||||
|
||||
## Dynamic Doors
|
||||
|
||||
Dynamic Doors will turn placed doors into actual working doors, Instead of them being static. (See videos below)
|
||||
|
||||
### Preview
|
||||
|
||||
https://github.com/complexza/ps-housing/assets/74205343/72cfc135-2f78-42b3-a540-45f02567b6d7
|
||||
|
||||
https://github.com/complexza/ps-housing/assets/74205343/0ff26e7f-1341-45fc-8fc6-d65421dec0b2
|
||||
|
||||
### Setup
|
||||
- You will need to set the `Config.DynamicDoors = true`
|
||||
- You will have to add this convar into your server.cfg `setr game_enableDynamicDoorCreation "true"`
|
||||
|
||||
> Note: The convar has to be in your server.cfg in order for the doors to be dynamic!
|
||||
|
||||
### Shell Support
|
||||
* [K4MB1](https://github.com/Project-Sloth/ps-housing/wiki/K4MB1-Shells-Support-&-Offsets)
|
||||
|
||||
# Important
|
||||
|
||||
* Players need to place their [stash](https://github.com/Project-Sloth/ps-housing/blob/7efd2009050b9a20969877cf69b284352a9309bf/shared/config.lua#LL426C96-L426C96) and [wardrobe](https://github.com/Project-Sloth/ps-housing/blob/7efd2009050b9a20969877cf69b284352a9309bf/shared/config.lua#L427) or else they wont have one. Check [Config](https://github.com/Project-Sloth/ps-housing/blob/7efd2009050b9a20969877cf69b284352a9309bf/shared/config.lua#L422) for more information.
|
||||
|
||||
* This entire README is meant for compatibility with default QBCore scripts. If you have different scripts, you'll need to adjust them for compatibility yourself. Refrain from asking us how to circumvent paid scripts that can't be adjusted for ps-housing support. Instead, request their support for ps-housing - this script is fully open source for that reason. Any inquiries related to this be ignored.
|
||||
|
||||
# Installation
|
||||
## PAY ATTENTION TO EACH STEP. DO NOT SKIP ANY.
|
||||
|
||||
1. Find the following events in `qb-multicharacter` and change in server/main.lua event to:
|
||||
|
||||
`qb-multicharacter > server > main.lua`
|
||||
```lua
|
||||
RegisterNetEvent('qb-multicharacter:server:loadUserData', function(cData)
|
||||
local src = source
|
||||
if QBCore.Player.Login(src, cData.citizenid) then
|
||||
repeat
|
||||
Wait(10)
|
||||
until hasDonePreloading[src]
|
||||
print('^2[qb-core]^7 '..GetPlayerName(src)..' (Citizen ID: '..cData.citizenid..') has succesfully loaded!')
|
||||
QBCore.Commands.Refresh(src)
|
||||
TriggerClientEvent('ps-housing:client:setupSpawnUI', src, cData)
|
||||
TriggerEvent("qb-log:server:CreateLog", "joinleave", "Loaded", "green", "**".. GetPlayerName(src) .. "** (<@"..(QBCore.Functions.GetIdentifier(src, 'discord'):gsub("discord:", "") or "unknown").."> | ||" ..(QBCore.Functions.GetIdentifier(src, 'ip') or 'undefined') .. "|| | " ..(QBCore.Functions.GetIdentifier(src, 'license') or 'undefined') .." | " ..cData.citizenid.." | "..src..") loaded..")
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
`qb-multicharacter > server > main.lua`
|
||||
```lua
|
||||
RegisterNetEvent('qb-multicharacter:server:createCharacter', function(data)
|
||||
local src = source
|
||||
local newData = {}
|
||||
newData.cid = data.cid
|
||||
newData.charinfo = data
|
||||
if QBCore.Player.Login(src, false, newData) then
|
||||
repeat
|
||||
Wait(10)
|
||||
until hasDonePreloading[src]
|
||||
print('^2[qb-core]^7 '..GetPlayerName(src)..' has succesfully loaded!')
|
||||
QBCore.Commands.Refresh(src)
|
||||
TriggerClientEvent("qb-multicharacter:client:closeNUI", src)
|
||||
newData.citizenid = QBCore.Functions.GetPlayer(src).PlayerData.citizenid
|
||||
TriggerClientEvent('ps-housing:client:setupSpawnUI', src, newData)
|
||||
GiveStarterItems(src)
|
||||
end
|
||||
end)
|
||||
```
|
||||
2. Find the following events in `qb-spawn` and change in client/client.lua event to:
|
||||
|
||||
`qb-spawn > client.lua > line 51 > 'qb-spawn:client:setupSpawns' event`
|
||||
```lua
|
||||
RegisterNetEvent('qb-spawn:client:setupSpawns', function(cData, new, apps)
|
||||
if not new then
|
||||
QBCore.Functions.TriggerCallback('qb-spawn:server:getOwnedHouses', function(houses)
|
||||
local myHouses = {}
|
||||
if houses ~= nil then
|
||||
for i = 1, (#houses), 1 do
|
||||
local house = houses[i]
|
||||
|
||||
myHouses[#myHouses+1] = {
|
||||
house = house,
|
||||
label = (house.apartment or house.street) .. " " .. house.property_id,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Wait(500)
|
||||
SendNUIMessage({
|
||||
action = "setupLocations",
|
||||
locations = QB.Spawns,
|
||||
houses = myHouses,
|
||||
isNew = new
|
||||
})
|
||||
end, cData.citizenid)
|
||||
elseif new then
|
||||
SendNUIMessage({
|
||||
action = "setupAppartements",
|
||||
locations = apps,
|
||||
isNew = new
|
||||
})
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
`qb-spawn > client.lua > line 134 > 'chooseAppa' NUI Callback`
|
||||
```lua
|
||||
RegisterNUICallback('chooseAppa', function(data, cb)
|
||||
local ped = PlayerPedId()
|
||||
local appaYeet = data.appType
|
||||
SetDisplay(false)
|
||||
DoScreenFadeOut(500)
|
||||
Wait(100)
|
||||
FreezeEntityPosition(ped, false)
|
||||
RenderScriptCams(false, true, 0, true, true)
|
||||
SetCamActive(cam, false)
|
||||
DestroyCam(cam, true)
|
||||
SetCamActive(cam2, false)
|
||||
DestroyCam(cam2, true)
|
||||
SetEntityVisible(ped, true)
|
||||
Wait(500)
|
||||
TriggerServerEvent('QBCore:Server:OnPlayerLoaded')
|
||||
TriggerEvent('QBCore:Client:OnPlayerLoaded')
|
||||
Wait(100)
|
||||
TriggerServerEvent("ps-housing:server:createNewApartment", appaYeet)
|
||||
cb('ok')
|
||||
end)
|
||||
```
|
||||
|
||||
`qb-spawn > client > client.lua > line 169 'spawnplayer' NUI Callback`
|
||||
```lua
|
||||
RegisterNUICallback('spawnplayer', function(data, cb)
|
||||
local location = tostring(data.spawnloc)
|
||||
local type = tostring(data.typeLoc)
|
||||
local ped = PlayerPedId()
|
||||
local PlayerData = QBCore.Functions.GetPlayerData()
|
||||
local insideMeta = PlayerData.metadata["inside"]
|
||||
if type == "current" then
|
||||
PreSpawnPlayer()
|
||||
QBCore.Functions.GetPlayerData(function(pd)
|
||||
ped = PlayerPedId()
|
||||
SetEntityCoords(ped, pd.position.x, pd.position.y, pd.position.z)
|
||||
SetEntityHeading(ped, pd.position.a)
|
||||
FreezeEntityPosition(ped, false)
|
||||
end)
|
||||
TriggerServerEvent('QBCore:Server:OnPlayerLoaded')
|
||||
TriggerEvent('QBCore:Client:OnPlayerLoaded')
|
||||
if insideMeta.property_id ~= nil then
|
||||
local property_id = insideMeta.property_id
|
||||
TriggerServerEvent('ps-housing:server:enterProperty', tostring(property_id))
|
||||
end
|
||||
PostSpawnPlayer()
|
||||
elseif type == "house" then
|
||||
PreSpawnPlayer()
|
||||
TriggerServerEvent('QBCore:Server:OnPlayerLoaded')
|
||||
TriggerEvent('QBCore:Client:OnPlayerLoaded')
|
||||
local property_id = data.spawnloc.property_id
|
||||
TriggerServerEvent('ps-housing:server:enterProperty', tostring(property_id))
|
||||
PostSpawnPlayer()
|
||||
elseif type == "normal" then
|
||||
local pos = QB.Spawns[location].coords
|
||||
PreSpawnPlayer()
|
||||
SetEntityCoords(ped, pos.x, pos.y, pos.z)
|
||||
TriggerServerEvent('QBCore:Server:OnPlayerLoaded')
|
||||
TriggerEvent('QBCore:Client:OnPlayerLoaded')
|
||||
TriggerServerEvent('ps-housing:server:resetMetaData')
|
||||
SetEntityCoords(ped, pos.x, pos.y, pos.z)
|
||||
SetEntityHeading(ped, pos.w)
|
||||
PostSpawnPlayer()
|
||||
end
|
||||
cb('ok')
|
||||
end)
|
||||
```
|
||||
|
||||
`qb-spawn > server.lua > line 3`
|
||||
```lua
|
||||
QBCore.Functions.CreateCallback('qb-spawn:server:getOwnedHouses', function(_, cb, cid)
|
||||
if cid ~= nil then
|
||||
local houses = MySQL.query.await('SELECT * FROM properties WHERE owner_citizenid = ?', {cid})
|
||||
if houses[1] ~= nil then
|
||||
cb(houses)
|
||||
else
|
||||
cb({})
|
||||
end
|
||||
else
|
||||
cb({})
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
3. Find the following events in `qb-garages` and change:
|
||||
`qb-garages > server > main.lua > around line 120` on event `qb-garage:server:checkOwnership`
|
||||
|
||||
Replace
|
||||
```lua
|
||||
local hasHouseKey = exports['qb-houses']:hasKey(result[1].license, result[1].citizenid, house)
|
||||
```
|
||||
With
|
||||
```lua
|
||||
local hasHouseKey = exports['ps-housing']:IsOwner(src, house)
|
||||
```
|
||||
|
||||
`qb-garages > client > main.lua > around line 451` under event `qb-garages:client:addHouseGarage`
|
||||
```lua
|
||||
RegisterNetEvent('qb-garages:client:removeHouseGarage', function(house)
|
||||
Config.HouseGarages[house] = nil
|
||||
end)
|
||||
```
|
||||
|
||||
4. Run the `properties.sql` file, but be cautious. If a table named `properties` already exists in your database, this operation will drop it, resulting in the loss of all its data.
|
||||
|
||||
5. Delete default [qb-apartments](https://github.com/qbcore-framework/qb-apartments)
|
||||
|
||||
6. Delete default [qb-houses](https://github.com/qbcore-framework/qb-houses)
|
||||
|
||||
7. Delete `qb-apartments/config.lua` references in `qb-spawn`, `qb-multicharacter` and `qb-phone` fxmanifest.lua (and any other scripts that may reference it).
|
||||
|
||||
8. Ensure ps-realtor above ps-housing.
|
||||
|
||||
9. In your server.cfg, add `ensure ox_lib` above all other resources
|
||||
|
||||
10. Install the dependencies below.
|
||||
|
||||
## Migrating houses and apartments from qb-houses and qb-apartments
|
||||
|
||||
1. From a client run the `migratehouses` command to automatically convert all houses from qb-houses. It will print a message to the console once complete.
|
||||
**The `migratehouses` command MUST be run from a client in order to retrieve street and region data for each house**
|
||||
|
||||
2. From a client or server console run the `migrateapartments` command to automatically convert all apartments from qb-apartments. It will print a message to the console once complete.
|
||||
|
||||
## Resolving the "Foreign key constraint is incorrectly formed" Issue
|
||||
|
||||
If you come across an error such as `Foreign key constraint is incorrectly formed` while importing the `properties.sql` into your database, follow these steps to fix it.
|
||||
|
||||
1. Open your database in HeidiSQL.
|
||||
2. Right-click on your database name and select "Edit."
|
||||
3. Locate the database collation setting take a note of it.
|
||||
4. You will need to format the `properties.sql` file to match your database collation.
|
||||
|
||||
If your database collation is set to `utf8mb4_general_ci`, modify the last line of the `properties.sql` file using VSCode or in HeidiSQL's query tab to the following:
|
||||
|
||||
```sql
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
```
|
||||
This adjustment ensures that `properties.sql` file's character set and collation match that of your database, effectively resolving the issue.
|
||||
|
||||
## Item Limits System
|
||||
|
||||
1. Choose an item you want to limit under `Config.Furniture` in under `shared/config.lua`
|
||||
2. Add `["max"] = 3` or the number of your choice to the item (see example below)
|
||||
```lua
|
||||
{ ["object"] = "v_res_r_figcat", ["price"] = 300, ["max"] = 2, ["label"] = "Fig Cat" },
|
||||
```
|
||||
|
||||
## Logs System Setup
|
||||
|
||||
1. Go to `qb-smallresources/server/logs.lua` and add this:
|
||||
```lua
|
||||
['pshousing'] = 'yourdiscordwebhookhere',
|
||||
```
|
||||
2. Create a webhook for the channel you want the logs to show up in.
|
||||
3. Replace the placeholder with your webhook link.
|
||||
|
||||
> This system only supports qb-core for now.
|
||||
|
||||
## Add new Shells
|
||||
|
||||
1. Copy the files from the shell into the `ps-housing/stream` folder.
|
||||
2. Add the new shell into the Config.Shells in `ps-housing/shared/config.lua`.
|
||||
|
||||
# Dependency
|
||||
1. [ps-realtor](https://github.com/Project-Sloth/ps-realtor)
|
||||
2. [five-freecam](https://github.com/Deltanic/fivem-freecam)
|
||||
3. [ox_lib](https://github.com/overextended/ox_lib/releases) - Use the latest release. If you do not use the latest release, MAKE SURE TO BUILD THE UI. Find their docs [here](https://overextended.dev/ox_lib#building-the-ui) on how to build the UI.
|
||||
4. [ox_target](https://github.com/overextended/ox_target) or [qb-target](https://github.com/qbcore-framework/qb-target) - Change in [Config](https://github.com/Project-Sloth/ps-housing/blob/3c0f197b6d639f13235598393c84aac8d23d5f7a/shared/config.lua#L8), default is qb-target.
|
||||
|
||||
## For reference your server.cfg should be ensured like below:
|
||||
* We highly recommend making a folder named [ps-housing] and add `ps-realtor`, `fivem-freecam`, `ox_lib`, `ps-core`, `ps-housing` inside the folder.
|
||||
|
||||
```
|
||||
ensure ox_lib
|
||||
ensure ps-housing
|
||||
ensure ps-realtor
|
||||
ensure fivem-freecam
|
||||
```
|
||||
|
||||
# Notes
|
||||
- If a player is in their apartment/house and an admin does a "Bring to me" function, they will not see the player nor will the player see anyone else. This is because the player is still in their own unique routing bucket. **Workaround**: To fix this, the player must go back into their apartment and leave on their own.
|
||||
- Likewise, if an admin tries to "Go to" or "Spectate" a player that is in their apartment/house, the admin will not be able to see the apartment or player because it is in a different routing bucket.
|
||||
# Credits
|
||||
* [Xirvin](https://github.com/ImXirvin)
|
||||
* [BackSH00TER](https://github.com/backsh00ter)
|
||||
* [Byte Labs Project](https://github.com/Byte-Labs-Project)
|
||||
* [Project Sloth Team](https://discord.gg/projectsloth)
|
||||
* [K4MB1](https://www.k4mb1maps.com/)
|
||||
* [Candrex](https://github.com/CandrexDev)
|
171
resources/[ps]/ps-housing/client/apartment.lua
Normal file
171
resources/[ps]/ps-housing/client/apartment.lua
Normal file
@ -0,0 +1,171 @@
|
||||
-- TBH I should have learnt how lua inheritance worked instead of making a new class but oh well. Maybe next time
|
||||
|
||||
Apartment = {
|
||||
apartmentData = {},
|
||||
apartments = {},
|
||||
|
||||
entranceTarget = nil,
|
||||
blip = nil,
|
||||
}
|
||||
Apartment.__index = Apartment
|
||||
|
||||
function Apartment:new(apartmentData)
|
||||
local self = setmetatable({}, Apartment)
|
||||
self.apartmentData = apartmentData
|
||||
self.apartments = {}
|
||||
|
||||
self:RegisterPropertyEntrance()
|
||||
self:CreateBlip()
|
||||
return self
|
||||
end
|
||||
|
||||
function Apartment:RegisterPropertyEntrance()
|
||||
local door = self.apartmentData.door
|
||||
local targetName = string.format("%s_apartment",self.apartmentData.label)
|
||||
|
||||
-- not sure why but referencing self directy runs it when registering the zones
|
||||
local function enterApartment()
|
||||
self:EnterApartment()
|
||||
end
|
||||
|
||||
local function seeAll()
|
||||
self:GetMenuForAll()
|
||||
end
|
||||
|
||||
local function seeAllToRaid()
|
||||
self:GetMenuForAllToRaid()
|
||||
end
|
||||
|
||||
local size = vector3(door.length, door.width, 3.0)
|
||||
local heading = door.h
|
||||
|
||||
Framework[Config.Target].AddApartmentEntrance(door, size, heading, self.apartmentData.label, enterApartment, seeAll, seeAllToRaid, targetName)
|
||||
end
|
||||
|
||||
function Apartment:EnterApartment()
|
||||
for propertyId, _ in pairs(self.apartments) do
|
||||
local property = PropertiesTable[propertyId]
|
||||
if property.owner then
|
||||
TriggerServerEvent('ps-housing:server:enterProperty', propertyId)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
Framework[Config.Notify].Notify("Du har ingen lejlighed her.", "error")
|
||||
end
|
||||
|
||||
function Apartment:GetMenuForAll()
|
||||
if next(self.apartments) == nil then
|
||||
Framework[Config.Notify].Notify("Der er ingen lejligheder her.", "error")
|
||||
return
|
||||
end
|
||||
|
||||
local id = "apartments-" .. self.apartmentData.label
|
||||
local menu = {
|
||||
id = id,
|
||||
title = "Lejligheder",
|
||||
options = {}
|
||||
}
|
||||
|
||||
for propertyId, _ in pairs(self.apartments) do
|
||||
table.insert(menu.options,{
|
||||
title = self.apartmentData.label .. " " .. propertyId,
|
||||
onSelect = function()
|
||||
TriggerServerEvent('ps-housing:server:enterProperty', propertyId)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
lib.registerContext(menu)
|
||||
lib.showContext(id)
|
||||
end
|
||||
|
||||
function Apartment:GetMenuForAllToRaid()
|
||||
if next(self.apartments) == nil then
|
||||
Framework[Config.Notify].Notify("Der er ingen lejligheder her.", "error")
|
||||
return
|
||||
end
|
||||
|
||||
local id = "apartments-" .. self.apartmentData.label
|
||||
local menu = {
|
||||
id = id,
|
||||
title = "Lejligheder til ransagning",
|
||||
options = {}
|
||||
}
|
||||
|
||||
for propertyId, _ in pairs(self.apartments) do
|
||||
table.insert(menu.options,{
|
||||
title = "Ransag " .. self.apartmentData.label .. " " .. propertyId,
|
||||
onSelect = function()
|
||||
TriggerServerEvent("ps-housing:server:raidProperty", propertyId)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
lib.registerContext(menu)
|
||||
lib.showContext(id)
|
||||
end
|
||||
|
||||
function Apartment:CreateBlip(hasProperty)
|
||||
self:DeleteBlip()
|
||||
|
||||
local door = self.apartmentData.door
|
||||
local blip = AddBlipForCoord(door.x, door.y, door.z)
|
||||
|
||||
SetBlipSprite(blip, 475)
|
||||
SetBlipScale(blip, 0.8)
|
||||
|
||||
if hasProperty then
|
||||
SetBlipColour(blip, 2)
|
||||
else
|
||||
SetBlipColour(blip, 4)
|
||||
end
|
||||
|
||||
SetBlipAsShortRange(blip, true)
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString(self.apartmentData.label)
|
||||
EndTextCommandSetBlipName(blip)
|
||||
self.blip = blip
|
||||
end
|
||||
|
||||
function Apartment:DeleteBlip()
|
||||
if not self.blip then return end
|
||||
RemoveBlip(self.blip)
|
||||
self.blip = nil
|
||||
end
|
||||
|
||||
function Apartment:AddProperty(propertyId)
|
||||
self.apartments[propertyId] = true
|
||||
|
||||
local property = Property.Get(propertyId)
|
||||
|
||||
if property ~= nil and property.owner then
|
||||
self:CreateBlip(true)
|
||||
end
|
||||
end
|
||||
|
||||
function Apartment:RemoveProperty(propertyId)
|
||||
if not self:HasProperty(propertyId) then return end
|
||||
self.apartments[propertyId] = nil
|
||||
|
||||
local property = Property.Get(propertyId)
|
||||
if property.owner then
|
||||
self:CreateBlip(false)
|
||||
end
|
||||
end
|
||||
|
||||
function Apartment:HasProperty(propertyId)
|
||||
return self.apartments[propertyId] ~= nil
|
||||
end
|
||||
|
||||
function Apartment:RemoveApartment()
|
||||
local targetName = string.format("%s_apartment",self.apartmentData.label)
|
||||
Framework[Config.Target].RemoveTargetZone(targetName)
|
||||
|
||||
self:DeleteBlip()
|
||||
self = {}
|
||||
end
|
||||
|
||||
function Apartment.Get(apartmentName)
|
||||
return ApartmentsTable[apartmentName]
|
||||
end
|
741
resources/[ps]/ps-housing/client/cl_property.lua
Normal file
741
resources/[ps]/ps-housing/client/cl_property.lua
Normal file
@ -0,0 +1,741 @@
|
||||
Property = {
|
||||
property_id = nil,
|
||||
propertyData = nil,
|
||||
|
||||
shell = nil,
|
||||
shellData = nil,
|
||||
inProperty = false,
|
||||
shellObj = nil,
|
||||
|
||||
has_access = false,
|
||||
owner = false,
|
||||
|
||||
storageTarget = nil,
|
||||
clothingTarget = nil,
|
||||
furnitureObjs = {},
|
||||
|
||||
garageZone = nil,
|
||||
doorbellPool = {},
|
||||
|
||||
entranceTarget = nil, -- needed for ox target
|
||||
exitTarget = nil, -- needed for ox target
|
||||
|
||||
blip = nil,
|
||||
}
|
||||
Property.__index = Property
|
||||
|
||||
function Property:new(propertyData)
|
||||
local self = setmetatable({}, Property)
|
||||
self.property_id = tostring(propertyData.property_id)
|
||||
|
||||
-- Remove furnitures from property data for memory purposes
|
||||
propertyData.furnitures = {}
|
||||
self.propertyData = propertyData
|
||||
|
||||
local citizenid = PlayerData.citizenid
|
||||
|
||||
self.owner = propertyData.owner == citizenid
|
||||
self.has_access = lib.table.contains(self.propertyData.has_access, citizenid)
|
||||
|
||||
if propertyData.apartment then
|
||||
local aptName = propertyData.apartment
|
||||
local apartment = ApartmentsTable[aptName]
|
||||
|
||||
if not apartment and Config.Apartments[aptName] then
|
||||
ApartmentsTable[aptName] = Apartment:new(Config.Apartments[aptName])
|
||||
apartment = ApartmentsTable[aptName]
|
||||
elseif not apartment then
|
||||
Debug(aptName .. " not found in Config")
|
||||
return
|
||||
end
|
||||
|
||||
apartment:AddProperty(self.property_id)
|
||||
else
|
||||
self:RegisterPropertyEntrance()
|
||||
self:RegisterGarageZone()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Property:GetDoorCoords()
|
||||
local coords = nil
|
||||
|
||||
local dataApartment = self.propertyData.apartment
|
||||
if dataApartment then
|
||||
local apartment = dataApartment
|
||||
coords = Config.Apartments[apartment].door
|
||||
else
|
||||
coords = self.propertyData.door_data
|
||||
end
|
||||
|
||||
return coords
|
||||
end
|
||||
|
||||
function Property:CreateShell()
|
||||
local coords = self:GetDoorCoords()
|
||||
|
||||
coords = vec3(coords.x, coords.y, coords.z - 25.0)
|
||||
self.shell = Shell:CreatePropertyShell(self.propertyData.shell, coords)
|
||||
|
||||
self.shellObj = self.shell.entity
|
||||
|
||||
local doorOffset = self.shellData.doorOffset
|
||||
local offset = GetOffsetFromEntityInWorldCoords(self.shellObj, doorOffset.x, doorOffset.y, doorOffset.z)
|
||||
self:RegisterDoorZone(offset)
|
||||
|
||||
SetEntityCoordsNoOffset(cache.ped, offset.x, offset.y, offset.z, false, false, true)
|
||||
SetEntityHeading(cache.ped, self.shellData.doorOffset.h)
|
||||
end
|
||||
|
||||
function Property:RegisterDoorZone(offset)
|
||||
local function leave()
|
||||
self:LeaveShell()
|
||||
end
|
||||
|
||||
local function checkDoor()
|
||||
self:OpenDoorbellMenu()
|
||||
end
|
||||
|
||||
local coords = offset
|
||||
local size = vector3(1.0, self.shellData.doorOffset.width, 3.0)
|
||||
local heading = self.shellData.doorOffset.h
|
||||
|
||||
self.exitTarget = Framework[Config.Target].AddDoorZoneInside(coords, size, heading, leave, checkDoor)
|
||||
end
|
||||
|
||||
function Property:RegisterPropertyEntrance()
|
||||
local door = self.propertyData.door_data
|
||||
local size = vector3(door.length, door.width, 2.5)
|
||||
local heading = door.h
|
||||
--Can be anon functions but I like to keep them named its more readable
|
||||
local function enter()
|
||||
TriggerServerEvent("ps-housing:server:enterProperty", self.property_id)
|
||||
end
|
||||
|
||||
local function raid()
|
||||
TriggerServerEvent("ps-housing:server:raidProperty", self.property_id)
|
||||
end
|
||||
|
||||
local function showcase()
|
||||
TriggerServerEvent("ps-housing:server:showcaseProperty", self.property_id)
|
||||
end
|
||||
|
||||
local function showData()
|
||||
local data = lib.callback.await("ps-housing:cb:getPropertyInfo", source, self.property_id)
|
||||
if not data then return end
|
||||
|
||||
local content = "**Ejer:** " .. data.owner .. " \n" .. "**Beskrivelse:** " .. data.description .. " \n" .. "**Gade:** " .. data.street .. " \n" .. "**Region:** " .. data.region .. " \n" .. "**Interiør:** " .. data.shell .. " \n" .. "**Til salg:** " .. (data.for_sale and "Ja" or "Nej")
|
||||
|
||||
if data.for_sale then
|
||||
content = content .. " \n" .. "**Pris:** " .. data.price.. ",-"
|
||||
end
|
||||
|
||||
lib.alertDialog({
|
||||
header = data.street .. " " .. data.property_id,
|
||||
content = content,
|
||||
centered = true,
|
||||
})
|
||||
end
|
||||
|
||||
local targetName = string.format("%s_%s", self.propertyData.street, self.property_id)
|
||||
|
||||
self.entranceTarget = Framework[Config.Target].AddEntrance(door, size, heading, self.property_id, enter, raid, showcase, showData, targetName)
|
||||
|
||||
if self.owner or self.has_access then
|
||||
self:CreateBlip()
|
||||
end
|
||||
end
|
||||
|
||||
function Property:UnregisterPropertyEntrance()
|
||||
if not self.entranceTarget then return end
|
||||
|
||||
Framework[Config.Target].RemoveTargetZone(self.entranceTarget)
|
||||
self.entranceTarget = nil
|
||||
end
|
||||
|
||||
function Property:RegisterGarageZone()
|
||||
if not next(self.propertyData.garage_data) then return end
|
||||
|
||||
if not (self.has_access or self.owner) or not self.owner then
|
||||
return
|
||||
end
|
||||
|
||||
local garageData = self.propertyData.garage_data
|
||||
local garageName = string.format("property-%s-garage", self.property_id)
|
||||
|
||||
local data = {
|
||||
takeVehicle = {
|
||||
x = garageData.x,
|
||||
y = garageData.y,
|
||||
z = garageData.z,
|
||||
w = garageData.h
|
||||
},
|
||||
type = "house",
|
||||
label = self.propertyData.street .. self.property_id .. " Garage",
|
||||
}
|
||||
|
||||
TriggerEvent("qb-garages:client:addHouseGarage", self.property_id, data)
|
||||
|
||||
self.garageZone = lib.zones.box({
|
||||
coords = vec3(garageData.x, garageData.y, garageData.z),
|
||||
size = vector3(garageData.length + 5.0, garageData.width + 5.0, 3.5),
|
||||
rotation = garageData.h,
|
||||
debug = Config.DebugMode,
|
||||
onEnter = function()
|
||||
TriggerEvent('qb-garages:client:setHouseGarage', self.property_id, true)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function Property:UnregisterGarageZone()
|
||||
if not self.garageZone then return end
|
||||
|
||||
TriggerEvent("qb-garages:client:removeHouseGarage", self.property_id)
|
||||
|
||||
self.garageZone:remove()
|
||||
self.garageZone = nil
|
||||
end
|
||||
|
||||
function Property:EnterShell()
|
||||
DoScreenFadeOut(250)
|
||||
TriggerServerEvent("InteractSound_SV:PlayOnSource", "houses_door_open", 0.25)
|
||||
Wait(250)
|
||||
|
||||
self.inProperty = true
|
||||
|
||||
self.shellData = Config.Shells[self.propertyData.shell]
|
||||
self:CreateShell()
|
||||
|
||||
self:LoadFurnitures()
|
||||
|
||||
self:GiveMenus()
|
||||
|
||||
Wait(250)
|
||||
DoScreenFadeIn(250)
|
||||
end
|
||||
|
||||
function Property:LeaveShell()
|
||||
if not self.inProperty then return end
|
||||
|
||||
DoScreenFadeOut(250)
|
||||
TriggerServerEvent("InteractSound_SV:PlayOnSource", "houses_door_open", 0.25)
|
||||
Wait(250)
|
||||
|
||||
local coords = self:GetDoorCoords()
|
||||
SetEntityCoordsNoOffset(cache.ped, coords.x, coords.y, coords.z, false, false, true)
|
||||
|
||||
TriggerServerEvent("ps-housing:server:leaveProperty", self.property_id)
|
||||
|
||||
self:UnloadFurnitures()
|
||||
self.propertyData.furnitures = {}
|
||||
|
||||
self.shell:DespawnShell()
|
||||
self.shell = nil
|
||||
if self.exitTarget then
|
||||
Framework[Config.Target].RemoveTargetZone(self.exitTarget)
|
||||
self.exitTarget = nil
|
||||
end
|
||||
|
||||
self:RemoveBlip()
|
||||
|
||||
self:RemoveMenus()
|
||||
|
||||
self.doorbellPool = {}
|
||||
|
||||
self.inProperty = false
|
||||
Wait(250)
|
||||
DoScreenFadeIn(250)
|
||||
end
|
||||
|
||||
function Property:GiveMenus()
|
||||
if not self.inProperty then return end
|
||||
|
||||
local accessAndConfig = self.has_access and Config.AccessCanEditFurniture
|
||||
|
||||
if self.owner or accessAndConfig then
|
||||
Framework[Config.Radial].AddRadialOption(
|
||||
"furniture_menu",
|
||||
"Indretning",
|
||||
"house-user",
|
||||
function()
|
||||
Modeler:OpenMenu(self.property_id)
|
||||
end,
|
||||
"ps-housing:client:openFurnitureMenu",
|
||||
{ propertyId = self.property_id }
|
||||
)
|
||||
end
|
||||
|
||||
if self.owner then
|
||||
Framework[Config.Radial].AddRadialOption(
|
||||
"access_menu",
|
||||
"Ejendomsadgang",
|
||||
"key",
|
||||
function()
|
||||
self:ManageAccessMenu()
|
||||
end,
|
||||
"ps-housing:client:openManagePropertyAccessMenu",
|
||||
{ propertyId = self.property_id }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Property:RemoveMenus()
|
||||
if not self.inProperty then return end
|
||||
|
||||
Framework[Config.Radial].RemoveRadialOption("furniture_menu")
|
||||
|
||||
if self.owner then
|
||||
Framework[Config.Radial].RemoveRadialOption("access_menu")
|
||||
end
|
||||
end
|
||||
|
||||
function Property:ManageAccessMenu()
|
||||
if not self.inProperty then return end
|
||||
|
||||
if not self.owner then
|
||||
Framework[Config.Notify].Notify("Kun ejeren kan gøre dette.", "error")
|
||||
return
|
||||
end
|
||||
|
||||
--Fuck qb-menu
|
||||
local id = "property-" .. self.property_id .. "-access"
|
||||
local menu = {
|
||||
id = id,
|
||||
title = "Administrer adgang",
|
||||
options = {},
|
||||
}
|
||||
|
||||
menu.options[#menu.options + 1] = {
|
||||
title = "Giv Adgang",
|
||||
onSelect = function()
|
||||
self:GiveAccessMenu()
|
||||
end,
|
||||
}
|
||||
|
||||
menu.options[#menu.options + 1] = {
|
||||
title = "Fjern Adgang",
|
||||
onSelect = function()
|
||||
self:RevokeAccessMenu()
|
||||
end,
|
||||
}
|
||||
|
||||
lib.registerContext(menu)
|
||||
lib.showContext(id)
|
||||
end
|
||||
|
||||
function Property:GiveAccessMenu()
|
||||
if not self.inProperty then return end
|
||||
|
||||
if not self.owner then
|
||||
return
|
||||
end
|
||||
|
||||
local id = "property-" .. self.property_id .. "-access-give"
|
||||
local menu = {
|
||||
id = id,
|
||||
title = "Giv Adgang",
|
||||
options = {},
|
||||
}
|
||||
|
||||
local players = lib.callback.await("ps-housing:cb:getPlayersInProperty", source, self.property_id) or {}
|
||||
|
||||
if #players > 0 then
|
||||
for i = 1, #players do
|
||||
local v = players[i]
|
||||
menu.options[#menu.options + 1] = {
|
||||
title = v.name,
|
||||
description = "Giv Adgang",
|
||||
onSelect = function()
|
||||
TriggerServerEvent("ps-housing:server:addAccess", self.property_id, v.src)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
lib.registerContext(menu)
|
||||
lib.showContext(id)
|
||||
else
|
||||
Framework[Config.Notify].Notify("Der er ingen i boligen", "error")
|
||||
end
|
||||
end
|
||||
|
||||
function Property:RevokeAccessMenu()
|
||||
if not self.owner then
|
||||
return
|
||||
end
|
||||
|
||||
local id = "property-" .. self.property_id .. "-access-already"
|
||||
local alreadyAccessMenu = {
|
||||
id = id,
|
||||
title = "Revoke Access",
|
||||
options = {},
|
||||
}
|
||||
|
||||
local playersWithAccess = lib.callback.await("ps-housing:cb:getPlayersWithAccess", source, self.property_id) or {}
|
||||
|
||||
-- only stores names and citizenids in a table so if their offline you can still remove them
|
||||
if #playersWithAccess > 0 then
|
||||
for i = 1, #playersWithAccess do
|
||||
local v = playersWithAccess[i]
|
||||
alreadyAccessMenu.options[#alreadyAccessMenu.options + 1] = {
|
||||
title = v.name,
|
||||
description = "Fjern Adgang",
|
||||
onSelect = function()
|
||||
TriggerServerEvent("ps-housing:server:removeAccess", self.property_id, v.citizenid)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
lib.registerContext(alreadyAccessMenu)
|
||||
lib.showContext(id)
|
||||
else
|
||||
Framework[Config.Notify].Notify("Ingen har adgang til denne ejendom", "error")
|
||||
end
|
||||
end
|
||||
|
||||
function Property:OpenDoorbellMenu()
|
||||
if not self.inProperty then return end
|
||||
|
||||
if not next(self.doorbellPool) then
|
||||
Framework[Config.Notify].Notify("Der er ingen ved døren", "error")
|
||||
return
|
||||
end
|
||||
|
||||
local id = string.format("property-%s-doorbell", self.property_id)
|
||||
local menu = {
|
||||
id = id,
|
||||
title = "Gæster ved døren",
|
||||
options = {},
|
||||
}
|
||||
|
||||
for k, v in pairs(self.doorbellPool) do
|
||||
menu.options[#menu.options + 1] = {
|
||||
title = v.name,
|
||||
onSelect = function()
|
||||
TriggerServerEvent(
|
||||
"ps-housing:server:doorbellAnswer",
|
||||
{ targetSrc = v.src, property_id = self.property_id }
|
||||
)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
lib.registerContext(menu)
|
||||
lib.showContext(id)
|
||||
end
|
||||
|
||||
function Property:LoadFurniture(furniture)
|
||||
local coords = GetOffsetFromEntityInWorldCoords(self.shellObj, furniture.position.x, furniture.position.y, furniture.position.z)
|
||||
local hash = furniture.object
|
||||
|
||||
lib.requestModel(hash)
|
||||
local entity = CreateObjectNoOffset(hash, coords.x, coords.y, coords.z, false, true, false)
|
||||
SetModelAsNoLongerNeeded(hash)
|
||||
SetEntityRotation(entity, furniture.rotation.x, furniture.rotation.y, furniture.rotation.z, 2, true)
|
||||
|
||||
if furniture.type == 'door' and Config.DynamicDoors then
|
||||
Debug("Object: "..furniture.label.." wont be frozen")
|
||||
else
|
||||
FreezeEntityPosition(entity, true)
|
||||
end
|
||||
|
||||
if furniture.type and Config.FurnitureTypes[furniture.type] then
|
||||
Config.FurnitureTypes[furniture.type](entity, self.property_id, self.propertyData.shell)
|
||||
end
|
||||
|
||||
self.furnitureObjs[#self.furnitureObjs + 1] = {
|
||||
entity = entity,
|
||||
id = furniture.id,
|
||||
label = furniture.label,
|
||||
object = furniture.object,
|
||||
position = {
|
||||
x = coords.x,
|
||||
y = coords.y,
|
||||
z = coords.z,
|
||||
},
|
||||
rotation = furniture.rotation,
|
||||
type = furniture.type,
|
||||
}
|
||||
end
|
||||
|
||||
function Property:LoadFurnitures()
|
||||
self.propertyData.furnitures = lib.callback.await('ps-housing:cb:getFurnitures', source, self.property_id) or {}
|
||||
|
||||
for i = 1, #self.propertyData.furnitures do
|
||||
local furniture = self.propertyData.furnitures[i]
|
||||
self:LoadFurniture(furniture)
|
||||
end
|
||||
end
|
||||
|
||||
function Property:UnloadFurniture(furniture, index)
|
||||
local entity = furniture?.entity
|
||||
if not entity then
|
||||
for i = 1, #self.furnitureObjs do
|
||||
if self.furnitureObjs[i]?.id and furniture?.id and self.furnitureObjs[i].id == furniture.id then
|
||||
entity = self.furnitureObjs[i]?.entity
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.clothingTarget == entity or self.storageTarget == entity then
|
||||
Framework[Config.Target].RemoveTargetEntity(entity)
|
||||
|
||||
if self.clothingTarget == entity then
|
||||
self.clothingTarget = nil
|
||||
elseif self.storageTarget == entity then
|
||||
self.storageTarget = nil
|
||||
end
|
||||
end
|
||||
|
||||
if index and self.furnitureObjs?[index] then
|
||||
table.remove(self.furnitureObjs, index)
|
||||
else
|
||||
for i = 1, #self.furnitureObjs do
|
||||
if self.furnitureObjs[i]?.id and furniture?.id and self.furnitureObjs[i].id == furniture.id then
|
||||
table.remove(self.furnitureObjs, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DeleteObject(entity)
|
||||
end
|
||||
|
||||
function Property:UnloadFurnitures()
|
||||
for i = 1, #self.furnitureObjs do
|
||||
local furniture = self.furnitureObjs[i]
|
||||
self:UnloadFurniture(furniture, i)
|
||||
end
|
||||
self.furnitureObjs = {}
|
||||
end
|
||||
|
||||
function Property:CreateBlip()
|
||||
local door_data = self.propertyData.door_data
|
||||
local blip = AddBlipForCoord(door_data.x, door_data.y, door_data.z)
|
||||
if self.propertyData.garage_data.x ~= nil then
|
||||
SetBlipSprite(blip, 492)
|
||||
else
|
||||
SetBlipSprite(blip, 40)
|
||||
end
|
||||
SetBlipScale(blip, 0.8)
|
||||
SetBlipColour(blip, 2)
|
||||
SetBlipAsShortRange(blip, true)
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString(self.propertyData.street .. " " .. self.property_id)
|
||||
EndTextCommandSetBlipName(blip)
|
||||
self.blip = blip
|
||||
end
|
||||
|
||||
function Property:RemoveBlip()
|
||||
if not self.blip then return end
|
||||
RemoveBlip(self.blip)
|
||||
self.blip = nil
|
||||
end
|
||||
|
||||
function Property:RemoveProperty()
|
||||
local targetName = string.format("%s_%s", self.propertyData.street, self.property_id)
|
||||
|
||||
Framework[Config.Target].RemoveTargetZone(targetName)
|
||||
|
||||
self:RemoveBlip()
|
||||
|
||||
self:LeaveShell()
|
||||
|
||||
--@@ comeback to this
|
||||
-- Think it works now
|
||||
if self.propertyData.apartment then
|
||||
ApartmentsTable[self.propertyData.apartment]:RemoveProperty()
|
||||
end
|
||||
|
||||
self = nil
|
||||
end
|
||||
|
||||
local function findFurnitureDifference(new, old)
|
||||
local added = {}
|
||||
local removed = {}
|
||||
|
||||
for i = 1, #new do
|
||||
local found = false
|
||||
for j = 1, #old do
|
||||
if new[i].id == old[j].id then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
added[#added + 1] = new[i]
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #old do
|
||||
local found = false
|
||||
for j = 1, #new do
|
||||
if old[i].id == new[j].id then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
removed[#removed + 1] = old[i]
|
||||
end
|
||||
end
|
||||
|
||||
return added, removed
|
||||
end
|
||||
|
||||
-- I think this whole furniture sync is a bit shit, but I cbf thinking
|
||||
function Property:UpdateFurnitures(newFurnitures)
|
||||
if not self.inProperty then return end
|
||||
|
||||
local oldFurnitures = self.propertyData.furnitures
|
||||
local added, removed = findFurnitureDifference(newFurnitures, oldFurnitures)
|
||||
|
||||
for i = 1, #added do
|
||||
local furniture = added[i]
|
||||
self:LoadFurniture(furniture)
|
||||
end
|
||||
|
||||
for i = 1, #removed do
|
||||
local furniture = removed[i]
|
||||
self:UnloadFurniture(furniture)
|
||||
end
|
||||
|
||||
self.propertyData.furnitures = newFurnitures
|
||||
|
||||
Modeler:UpdateFurnitures()
|
||||
end
|
||||
|
||||
function Property:UpdateDescription(newDescription)
|
||||
self.propertyData.description = newDescription
|
||||
end
|
||||
|
||||
function Property:UpdatePrice(newPrice)
|
||||
self.propertyData.price = newPrice
|
||||
end
|
||||
|
||||
function Property:UpdateForSale(forSale)
|
||||
self.propertyData.for_sale = forSale
|
||||
end
|
||||
|
||||
function Property:UpdateShell(newShell)
|
||||
self:LeaveShell()
|
||||
self.propertyData.shell = newShell
|
||||
|
||||
if self.inProperty then
|
||||
self:EnterShell()
|
||||
end
|
||||
end
|
||||
|
||||
function Property:UpdateOwner(newOwner)
|
||||
self.propertyData.owner = newOwner
|
||||
|
||||
local citizenid = PlayerData.citizenid
|
||||
|
||||
self.owner = newOwner == citizenid
|
||||
|
||||
self:UnregisterGarageZone()
|
||||
self:RegisterGarageZone()
|
||||
|
||||
self:CreateBlip()
|
||||
|
||||
if not self.inProperty then return end
|
||||
|
||||
self:RemoveMenus()
|
||||
self:GiveMenus()
|
||||
end
|
||||
|
||||
function Property:UpdateImgs(newImgs)
|
||||
self.propertyData.imgs = newImgs
|
||||
end
|
||||
|
||||
function Property:UpdateDoor(newDoor, newStreet, newRegion)
|
||||
self.propertyData.door_data = newDoor
|
||||
self.propertyData.street = newStreet
|
||||
self.propertyData.region = newRegion
|
||||
|
||||
self:UnregisterPropertyEntrance()
|
||||
self:RegisterPropertyEntrance()
|
||||
end
|
||||
|
||||
function Property:UpdateHas_access(newHas_access)
|
||||
local citizenid = PlayerData.citizenid
|
||||
self.propertyData.has_access = newHas_access
|
||||
self.has_access = lib.table.contains(newHas_access, citizenid)
|
||||
|
||||
if not self.inProperty then return end
|
||||
|
||||
self:RemoveMenus()
|
||||
self:GiveMenus()
|
||||
end
|
||||
|
||||
function Property:UpdateGarage(newGarage)
|
||||
self.propertyData.garage_data = newGarage
|
||||
|
||||
self:UnregisterGarageZone()
|
||||
self:RegisterGarageZone()
|
||||
end
|
||||
|
||||
function Property:UpdateApartment(newApartment)
|
||||
self:LeaveShell()
|
||||
|
||||
local oldAptName = self.propertyData.apartment
|
||||
local oldApt = ApartmentsTable[oldAptName]
|
||||
if oldApt then
|
||||
oldApt:RemoveProperty(self.property_id)
|
||||
end
|
||||
|
||||
self.propertyData.apartment = newApartment
|
||||
|
||||
local newApt = ApartmentsTable[newApartment]
|
||||
|
||||
if newApt then
|
||||
newApt:AddProperty(self.property_id)
|
||||
end
|
||||
|
||||
TriggerEvent("ps-housing:client:updateApartment", oldAptName, newApartment)
|
||||
end
|
||||
|
||||
function Property.Get(property_id)
|
||||
return PropertiesTable[tostring(property_id)]
|
||||
end
|
||||
|
||||
RegisterNetEvent("ps-housing:client:enterProperty", function(property_id)
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if property ~= nil then
|
||||
property:EnterShell()
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:client:updateDoorbellPool", function(property_id, data)
|
||||
local property = Property.Get(property_id)
|
||||
property.doorbellPool = data
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:client:updateFurniture", function(property_id, furnitures)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
property:UpdateFurnitures(furnitures)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:client:updateProperty", function(type, property_id, data)
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then return end
|
||||
|
||||
property[type](property, data)
|
||||
|
||||
TriggerEvent("ps-housing:client:updatedProperty", property_id)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:client:openFurnitureMenu", function(data)
|
||||
Modeler:OpenMenu(data.options.propertyId)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:client:openManagePropertyAccessMenu", function(data)
|
||||
local property = Property.Get(data.options.propertyId)
|
||||
if not property then return end
|
||||
|
||||
property:ManageAccessMenu()
|
||||
end)
|
157
resources/[ps]/ps-housing/client/client.lua
Normal file
157
resources/[ps]/ps-housing/client/client.lua
Normal file
@ -0,0 +1,157 @@
|
||||
QBCore = exports['qb-core']:GetCoreObject()
|
||||
PlayerData = {}
|
||||
|
||||
local function createProperty(property)
|
||||
PropertiesTable[property.property_id] = Property:new(property)
|
||||
end
|
||||
RegisterNetEvent('ps-housing:client:addProperty', createProperty)
|
||||
|
||||
RegisterNetEvent('ps-housing:client:removeProperty', function (property_id)
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if property then
|
||||
property:RemoveProperty()
|
||||
end
|
||||
|
||||
PropertiesTable[property_id] = nil
|
||||
end)
|
||||
|
||||
function InitialiseProperties(properties)
|
||||
Debug("Initialising properties")
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
|
||||
for k, v in pairs(Config.Apartments) do
|
||||
ApartmentsTable[k] = Apartment:new(v)
|
||||
end
|
||||
|
||||
if not properties then
|
||||
properties = lib.callback.await('ps-housing:server:requestProperties')
|
||||
end
|
||||
|
||||
for k, v in pairs(properties) do
|
||||
createProperty(v.propertyData)
|
||||
end
|
||||
|
||||
TriggerEvent("ps-housing:client:initialisedProperties")
|
||||
|
||||
Debug("Initialised properties")
|
||||
end
|
||||
AddEventHandler("QBCore:Client:OnPlayerLoaded", InitialiseProperties)
|
||||
RegisterNetEvent('ps-housing:client:initialiseProperties', InitialiseProperties)
|
||||
|
||||
AddEventHandler("onResourceStart", function(resourceName) -- Used for when the resource is restarted while in game
|
||||
if (GetCurrentResourceName() == resourceName) then
|
||||
InitialiseProperties()
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(job)
|
||||
PlayerData.job = job
|
||||
end)
|
||||
|
||||
RegisterNetEvent('ps-housing:client:setupSpawnUI', function(cData)
|
||||
DoScreenFadeOut(1000)
|
||||
local result = lib.callback.await('ps-housing:cb:GetOwnedApartment', source, cData.citizenid)
|
||||
if result then
|
||||
TriggerEvent('qb-spawn:client:setupSpawns', cData, false, nil)
|
||||
TriggerEvent('qb-spawn:client:openUI', true)
|
||||
-- TriggerEvent("apartments:client:SetHomeBlip", result.type)
|
||||
else
|
||||
if Config.StartingApartment then
|
||||
TriggerEvent('qb-spawn:client:setupSpawns', cData, true, Config.Apartments)
|
||||
TriggerEvent('qb-spawn:client:openUI', true)
|
||||
else
|
||||
TriggerEvent('qb-spawn:client:setupSpawns', cData, false, nil)
|
||||
TriggerEvent('qb-spawn:client:openUI', true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resourceName)
|
||||
if (GetCurrentResourceName() == resourceName) then
|
||||
if Modeler.IsMenuActive then
|
||||
Modeler:CloseMenu()
|
||||
end
|
||||
|
||||
for k, v in pairs(PropertiesTable) do
|
||||
v:RemoveProperty()
|
||||
end
|
||||
|
||||
for k, v in pairs(ApartmentsTable) do
|
||||
v:RemoveApartment()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
exports('GetProperties', function()
|
||||
return PropertiesTable
|
||||
end)
|
||||
|
||||
exports('GetProperty', function(property_id)
|
||||
return Property.Get(property_id)
|
||||
end)
|
||||
|
||||
exports('GetApartments', function()
|
||||
return ApartmentsTable
|
||||
end)
|
||||
|
||||
exports('GetApartment', function(apartment)
|
||||
return Apartment.Get(apartment)
|
||||
end)
|
||||
|
||||
exports('GetShells', function()
|
||||
return Config.Shells
|
||||
end)
|
||||
|
||||
|
||||
lib.callback.register('ps-housing:cb:confirmPurchase', function(amount, street, id)
|
||||
return lib.alertDialog({
|
||||
header = 'Bekræft Køb',
|
||||
content = 'Er du sikker på at du vil købe '..street..' ' .. id .. ' for ' .. amount .. ' kr?',
|
||||
centered = true,
|
||||
cancel = true,
|
||||
labels = {
|
||||
confirm = "Køb",
|
||||
cancel = "Annuller"
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
lib.callback.register('ps-housing:cb:confirmRaid', function(street, id)
|
||||
return lib.alertDialog({
|
||||
header = 'Ransag',
|
||||
content = 'Vil du ransage '..street..' ' .. id .. '?',
|
||||
centered = true,
|
||||
cancel = true,
|
||||
labels = {
|
||||
confirm = "Ransag",
|
||||
cancel = "Annuller"
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
lib.callback.register('ps-housing:cb:ringDoorbell', function()
|
||||
return lib.alertDialog({
|
||||
header = 'Ring på dørklokken',
|
||||
content = 'Du har ingen nøgle her til, vil du ringe på dørklokken??',
|
||||
centered = true,
|
||||
cancel = true,
|
||||
labels = {
|
||||
confirm = "Ring",
|
||||
cancel = "Annuller"
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
lib.callback.register('ps-housing:cb:showcase', function()
|
||||
return lib.alertDialog({
|
||||
header = 'Vis Ejendom',
|
||||
content = 'Vil du have vist ejendommen?',
|
||||
centered = true,
|
||||
cancel = true,
|
||||
labels = {
|
||||
confirm = "Ja",
|
||||
cancel = "Annuller"
|
||||
}
|
||||
})
|
||||
end)
|
6
resources/[ps]/ps-housing/client/migrate.lua
Normal file
6
resources/[ps]/ps-housing/client/migrate.lua
Normal file
@ -0,0 +1,6 @@
|
||||
lib.callback.register('ps-housing:client:getclientdata', function(data)
|
||||
return {
|
||||
street = GetStreetNameFromHashKey(GetStreetNameAtCoord(data.coords.x, data.coords.y, data.coords.z)),
|
||||
region = GetLabelText(GetNameOfZone(data.coords.x, data.coords.y, data.coords.z))
|
||||
}
|
||||
end)
|
646
resources/[ps]/ps-housing/client/modeler.lua
Normal file
646
resources/[ps]/ps-housing/client/modeler.lua
Normal file
@ -0,0 +1,646 @@
|
||||
local Freecam = exports['fivem-freecam']
|
||||
|
||||
local function CamThread()
|
||||
CreateThread(function()
|
||||
local IsDisabledControlJustPressed = IsDisabledControlJustPressed
|
||||
local DisableControlAction = DisableControlAction
|
||||
while Modeler.IsFreecamMode do
|
||||
if IsDisabledControlJustPressed(0, 26) then -- C
|
||||
Modeler.IsFreecamMode = false
|
||||
Modeler:FreecamMode(false)
|
||||
break
|
||||
end
|
||||
DisableControlAction(0, 199, true) -- P
|
||||
DisableControlAction(0, 200, true) -- ESC
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function isInside(coords)
|
||||
local extent = Modeler.shellMinMax
|
||||
|
||||
local isX = coords.x >= extent.min.x and coords.x <= extent.max.x
|
||||
local isY = coords.y >= extent.min.y and coords.y <= extent.max.y
|
||||
local isZ = coords.z >= extent.min.z and coords.z <= extent.max.z
|
||||
if isX and isY and isZ then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
local function getMinMax(shellPos, shellMin, shellMax)
|
||||
local min = vector3(shellPos.x + shellMin.x, shellPos.y + shellMin.y, shellPos.z + shellMin.z)
|
||||
local max = vector3(shellPos.x + shellMax.x, shellPos.y + shellMax.y, shellPos.z + shellMax.z)
|
||||
|
||||
return {min = min, max = max}
|
||||
end
|
||||
|
||||
|
||||
AddEventHandler('freecam:onTick', function()
|
||||
if not Modeler.IsFreecamMode then return end
|
||||
|
||||
local update = true
|
||||
local lookAt = Freecam:GetTarget(5.0)
|
||||
local camPos = Freecam:GetPosition()
|
||||
|
||||
-- see if camPos is the same as the last one
|
||||
if Modeler.CurrentCameraPosition and Modeler.CurrentCameraLookAt then
|
||||
local posX = Modeler.CurrentCameraPosition.x == camPos.x
|
||||
local posY = Modeler.CurrentCameraPosition.y == camPos.y
|
||||
local posZ = Modeler.CurrentCameraPosition.z == camPos.z
|
||||
|
||||
local lookAtX = Modeler.CurrentCameraLookAt.x == lookAt.x
|
||||
local lookAtY = Modeler.CurrentCameraLookAt.y == lookAt.y
|
||||
local lookAtZ = Modeler.CurrentCameraLookAt.z == lookAt.z
|
||||
|
||||
if posX and posY and posZ and lookAtX and lookAtY and lookAtZ then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not isInside(camPos) then
|
||||
Freecam:SetPosition(Modeler.CurrentCameraPosition.x, Modeler.CurrentCameraPosition.y, Modeler.CurrentCameraPosition.z)
|
||||
update = false
|
||||
end
|
||||
|
||||
if update then
|
||||
Modeler.CurrentCameraLookAt = lookAt
|
||||
Modeler.CurrentCameraPosition = camPos
|
||||
end
|
||||
|
||||
SendNUIMessage({
|
||||
action = "updateCamera",
|
||||
data = {
|
||||
cameraPosition = Modeler.CurrentCameraPosition,
|
||||
cameraLookAt = Modeler.CurrentCameraLookAt,
|
||||
}
|
||||
})
|
||||
end)
|
||||
|
||||
|
||||
|
||||
|
||||
-- WHERE THE ACTUAL CLASS STARTS
|
||||
|
||||
Modeler = {
|
||||
IsMenuActive = false,
|
||||
IsFreecamMode = false,
|
||||
|
||||
property_id = nil,
|
||||
|
||||
shellPos = nil,
|
||||
shellMinMax = nil,
|
||||
|
||||
CurrentObject = nil,
|
||||
CurrentCameraPosition = nil,
|
||||
CurrentCameraLookAt = nil,
|
||||
CurrentObjectAlpha = 200,
|
||||
|
||||
Cart = {},
|
||||
|
||||
-- Hover stuff
|
||||
IsHovering = false,
|
||||
HoverObject = nil,
|
||||
HoverDistance = 5.0,
|
||||
|
||||
OpenMenu = function(self, property_id)
|
||||
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then return end
|
||||
if not property.owner and not property.has_access then return end
|
||||
if property.has_access and not Config.AccessCanEditFurniture then return end
|
||||
|
||||
self.shellPos = GetEntityCoords(property.shellObj)
|
||||
local min, max = GetModelDimensions(property.shellData.hash)
|
||||
|
||||
self.shellMinMax = getMinMax(self.shellPos, min, max)
|
||||
|
||||
self.property_id = property_id
|
||||
self.IsMenuActive = true
|
||||
|
||||
self:UpdateFurnitures()
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setVisible",
|
||||
data = true
|
||||
})
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setFurnituresData",
|
||||
data = Config.Furnitures
|
||||
})
|
||||
|
||||
-- Owned furniture is set by the Property class
|
||||
SetNuiFocus(true, true)
|
||||
self:FreecamActive(true)
|
||||
self:FreecamMode(false)
|
||||
end,
|
||||
|
||||
CloseMenu = function(self)
|
||||
self.IsMenuActive = false
|
||||
self:ClearCart()
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setOwnedItems",
|
||||
data = {},
|
||||
})
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setVisible",
|
||||
data = false
|
||||
})
|
||||
|
||||
SetNuiFocus(false, false)
|
||||
|
||||
self:HoverOut()
|
||||
self:StopPlacement()
|
||||
self:FreecamActive(false)
|
||||
|
||||
Wait(500)
|
||||
|
||||
self.CurrentCameraPosition = nil
|
||||
self.CurrentCameraLookAt = nil
|
||||
self.CurrentObject = nil
|
||||
self.property_id = nil
|
||||
end,
|
||||
|
||||
FreecamActive = function(self, bool)
|
||||
if bool then
|
||||
Freecam:SetActive(true)
|
||||
Freecam:SetKeyboardSetting('BASE_MOVE_MULTIPLIER', 0.1)
|
||||
Freecam:SetKeyboardSetting('FAST_MOVE_MULTIPLIER', 2)
|
||||
Freecam:SetKeyboardSetting('SLOW_MOVE_MULTIPLIER', 2)
|
||||
Freecam:SetFov(45.0)
|
||||
self.IsFreecamMode = true
|
||||
else
|
||||
Freecam:SetActive(false)
|
||||
--reset to default
|
||||
Freecam:SetKeyboardSetting('BASE_MOVE_MULTIPLIER', 5)
|
||||
Freecam:SetKeyboardSetting('FAST_MOVE_MULTIPLIER', 10)
|
||||
Freecam:SetKeyboardSetting('SLOW_MOVE_MULTIPLIER', 10)
|
||||
self.IsFreecamMode = false
|
||||
end
|
||||
end,
|
||||
|
||||
FreecamMode = function(self, bool)
|
||||
if bool then --not in UI
|
||||
self.IsFreecamMode = true
|
||||
CamThread()
|
||||
Freecam:SetFrozen(false)
|
||||
SetNuiFocus(false, false)
|
||||
else -- in UI
|
||||
self.IsFreecamMode = false
|
||||
Freecam:SetFrozen(true)
|
||||
SetNuiFocus(true, true)
|
||||
SendNUIMessage({
|
||||
action = "freecamMode",
|
||||
data = false
|
||||
})
|
||||
end
|
||||
end,
|
||||
|
||||
StartPlacement = function(self, data)
|
||||
self:HoverOut() -- stops the hover effect on the previous object because sometimes mouseleave doesnt work
|
||||
local object = data.object
|
||||
local curObject
|
||||
local objectRot
|
||||
local objectPos
|
||||
|
||||
Modeler.CurrentCameraLookAt = Freecam:GetTarget(5.0)
|
||||
Modeler.CurrentCameraPosition = Freecam:GetPosition()
|
||||
|
||||
if data.entity then --if the object is already spawned
|
||||
curObject = data.entity
|
||||
objectPos = GetEntityCoords(curObject)
|
||||
objectRot = GetEntityRotation(curObject)
|
||||
else
|
||||
self:StopPlacement()
|
||||
lib.requestModel(object)
|
||||
|
||||
curObject = CreateObject(GetHashKey(object), 0.0, 0.0, 0.0, false, true, false)
|
||||
SetEntityCoords(curObject, self.CurrentCameraLookAt.x, self.CurrentCameraLookAt.y, self.CurrentCameraLookAt.z)
|
||||
|
||||
objectRot = GetEntityRotation(curObject)
|
||||
objectPos = self.CurrentCameraLookAt
|
||||
end
|
||||
|
||||
FreezeEntityPosition(curObject, true)
|
||||
SetEntityCollision(curObject, false, false)
|
||||
SetEntityAlpha(curObject, self.CurrentObjectAlpha, false)
|
||||
SetEntityDrawOutline(curObject, true)
|
||||
SetEntityDrawOutlineColor(255, 255, 255, 255)
|
||||
SetEntityDrawOutlineShader(0)
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setObjectAlpha",
|
||||
data = self.CurrentObjectAlpha
|
||||
})
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setupModel",
|
||||
data = {
|
||||
objectPosition = objectPos,
|
||||
objectRotation = objectRot,
|
||||
cameraPosition = self.CurrentCameraPosition,
|
||||
cameraLookAt = self.CurrentCameraLookAt,
|
||||
entity = data.entity,
|
||||
}
|
||||
})
|
||||
|
||||
SetNuiFocus(true, true)
|
||||
self.CurrentObject = curObject
|
||||
end,
|
||||
|
||||
MoveObject = function (self, data)
|
||||
local coords = vec3(data.x + 0.0, data.y + 0.0, data.z + 0.0)
|
||||
if not isInside(coords) then
|
||||
return
|
||||
end
|
||||
|
||||
SetEntityCoords(self.CurrentObject, coords)
|
||||
-- get the current offset of this object in relation to the
|
||||
end,
|
||||
|
||||
RotateObject = function (self, data)
|
||||
SetEntityRotation(self.CurrentObject, data.x + 0.0, data.y + 0.0, data.z + 0.0)
|
||||
end,
|
||||
|
||||
StopPlacement = function (self)
|
||||
if self.CurrentObject == nil then return end
|
||||
|
||||
local canDelete = true
|
||||
for k, v in pairs(self.Cart) do
|
||||
if k == self.CurrentObject then
|
||||
canDelete = false
|
||||
break
|
||||
end
|
||||
end
|
||||
-- furnitureObjs
|
||||
-- see if its an owned object
|
||||
local property = PropertiesTable[self.property_id]
|
||||
local ownedfurnitures = property.furnitureObjs
|
||||
for i = 1, #ownedfurnitures do
|
||||
if ownedfurnitures[i].entity == self.CurrentObject then
|
||||
self:UpdateFurniture(ownedfurnitures[i])
|
||||
canDelete = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if canDelete then
|
||||
DeleteEntity(self.CurrentObject)
|
||||
end
|
||||
|
||||
SetEntityDrawOutline(self.CurrentObject, false)
|
||||
SetEntityAlpha(self.CurrentObject, 255, false)
|
||||
self.CurrentObject = nil
|
||||
end,
|
||||
|
||||
UpdateFurnitures = function(self)
|
||||
|
||||
if not self.IsMenuActive then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.property_id then return end
|
||||
|
||||
local property = Property.Get(self.property_id)
|
||||
|
||||
if not property then return end
|
||||
if not property.owner and not property.has_access then return end
|
||||
if property.has_access and not Config.AccessCanEditFurniture then return end
|
||||
|
||||
|
||||
SendNUIMessage({
|
||||
action = "setOwnedItems",
|
||||
data = property.furnitureObjs,
|
||||
})
|
||||
end,
|
||||
|
||||
-- can be better
|
||||
-- everytime "Stop Placement" is pressed on an owned object, it will update the furniture
|
||||
-- maybe should do it all at once when the user leaves the menu????
|
||||
UpdateFurniture = function (self, item)
|
||||
local newPos = GetEntityCoords(item.entity)
|
||||
local newRot = GetEntityRotation(item.entity)
|
||||
|
||||
local offsetPos = {
|
||||
x = math.floor((newPos.x - self.shellPos.x) * 10000) / 10000,
|
||||
y = math.floor((newPos.y - self.shellPos.y) * 10000) / 10000,
|
||||
z = math.floor((newPos.z - self.shellPos.z) * 10000) / 10000,
|
||||
}
|
||||
|
||||
local newFurniture = {
|
||||
id = item.id,
|
||||
label = item.label,
|
||||
object = item.object,
|
||||
position = offsetPos,
|
||||
rotation = newRot,
|
||||
type = item.type,
|
||||
}
|
||||
|
||||
TriggerServerEvent("ps-housing:server:updateFurniture", self.property_id, newFurniture)
|
||||
end,
|
||||
|
||||
SetObjectAlpha = function (self, data)
|
||||
self.CurrentObjectAlpha = data.alpha
|
||||
SetEntityAlpha(self.CurrentObject, self.CurrentObjectAlpha, false)
|
||||
end,
|
||||
|
||||
PlaceOnGround = function (self)
|
||||
local x, y, z = table.unpack(GetEntityCoords(self.CurrentObject))
|
||||
local ground, z = GetGroundZFor_3dCoord(x, y, z, 0)
|
||||
SetEntityCoords(self.CurrentObject, x, y, z)
|
||||
|
||||
return {x = x, y = y, z = z}
|
||||
end,
|
||||
|
||||
SelectCartItem = function (self, data)
|
||||
self:StopPlacement()
|
||||
|
||||
if data ~= nil then
|
||||
self:StartPlacement(data)
|
||||
end
|
||||
end,
|
||||
|
||||
AddToCart = function (self, data)
|
||||
local item = {
|
||||
label = data.label,
|
||||
object = data.object,
|
||||
price = data.price,
|
||||
entity = self.CurrentObject,
|
||||
position = GetEntityCoords(self.CurrentObject),
|
||||
rotation = GetEntityRotation(self.CurrentObject),
|
||||
type = data.type,
|
||||
}
|
||||
|
||||
self.Cart[self.CurrentObject] = item
|
||||
|
||||
SendNUIMessage({
|
||||
action = "addToCart",
|
||||
data = item
|
||||
})
|
||||
|
||||
self:StopPlacement()
|
||||
self.CurrentObject = nil
|
||||
end,
|
||||
|
||||
RemoveFromCart = function (self, data)
|
||||
local item = data
|
||||
|
||||
if item ~= nil then
|
||||
DeleteEntity(item.entity)
|
||||
|
||||
SendNUIMessage({
|
||||
action = "removeFromCart",
|
||||
data = item
|
||||
})
|
||||
|
||||
self.Cart[data.entity] = nil
|
||||
end
|
||||
end,
|
||||
|
||||
UpdateCartItem = function (self, data)
|
||||
local item = self.Cart[data.entity]
|
||||
|
||||
if item ~= nil then
|
||||
item = data
|
||||
end
|
||||
end,
|
||||
|
||||
ClearCart = function (self)
|
||||
for _, v in pairs(self.Cart) do
|
||||
DeleteEntity(v.entity)
|
||||
end
|
||||
|
||||
self.Cart = {}
|
||||
SendNUIMessage({
|
||||
action = "clearCart"
|
||||
})
|
||||
end,
|
||||
|
||||
BuyCart = function (self)
|
||||
local items = {}
|
||||
local totalPrice = 0
|
||||
|
||||
-- If the cart is empty, return notify
|
||||
if not next(self.Cart) then
|
||||
Framework[Config.Notify].Notify("Din kurv er tom", "error")
|
||||
return
|
||||
end
|
||||
|
||||
-- seperate loop to get total price so it doesnt have to do all that math for no reason
|
||||
for _, v in pairs(self.Cart) do
|
||||
totalPrice = totalPrice + v.price
|
||||
end
|
||||
|
||||
PlayerData = QBCore.Functions.GetPlayerData()
|
||||
if PlayerData.money.cash < totalPrice and PlayerData.money.bank < totalPrice then
|
||||
Framework[Config.Notify].Notify("Du har ikke nok penge!", "error")
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in pairs(self.Cart) do
|
||||
|
||||
local offsetPos = {
|
||||
x = math.floor((v.position.x - self.shellPos.x) * 10000) / 10000,
|
||||
y = math.floor((v.position.y - self.shellPos.y) * 10000) / 10000,
|
||||
z = math.floor((v.position.z - self.shellPos.z) * 10000) / 10000,
|
||||
}
|
||||
|
||||
local id = tostring(math.random(100000, 999999)..self.property_id)
|
||||
|
||||
items[#items + 1] = {
|
||||
id = id,
|
||||
object = v.object,
|
||||
label = v.label,
|
||||
position = offsetPos,
|
||||
rotation = v.rotation,
|
||||
type = v.type,
|
||||
}
|
||||
end
|
||||
|
||||
TriggerServerEvent("ps-housing:server:buyFurniture", self.property_id, items, totalPrice)
|
||||
|
||||
self:ClearCart()
|
||||
end,
|
||||
|
||||
SetHoverDistance = function (self, data)
|
||||
self.HoverDistance = data + 0.0
|
||||
end,
|
||||
|
||||
HoverIn = function (self, data)
|
||||
if self.HoverObject then
|
||||
local tries = 0
|
||||
while DoesEntityExist(self.HoverObject) do
|
||||
SetEntityAsMissionEntity(self.HoverObject, true, true)
|
||||
DeleteEntity(self.HoverObject)
|
||||
Wait(50)
|
||||
tries = tries + 1
|
||||
if tries > 25 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.HoverObject = nil
|
||||
end
|
||||
|
||||
local object = data.object and joaat(data.object) or nil
|
||||
if object == nil then return end
|
||||
lib.requestModel(object)
|
||||
if self.HoverObject then return end
|
||||
self.HoverObject = CreateObject(object, 0.0, 0.0, 0.0, false, false, false)
|
||||
Modeler.CurrentCameraLookAt = Freecam:GetTarget(self.HoverDistance)
|
||||
local camRot = Freecam:GetRotation()
|
||||
|
||||
SetEntityCoords(self.HoverObject, self.CurrentCameraLookAt.x, self.CurrentCameraLookAt.y, self.CurrentCameraLookAt.z)
|
||||
FreezeEntityPosition(self.HoverObject, true)
|
||||
SetEntityCollision(self.HoverObject, false, false)
|
||||
SetEntityRotation(self.HoverObject, 0.0, 0.0, camRot.z)
|
||||
|
||||
self.IsHovering = true
|
||||
while self.IsHovering do
|
||||
local rot = GetEntityRotation(self.HoverObject)
|
||||
SetEntityRotation(self.HoverObject, rot.x, rot.y, rot.z + 0.1)
|
||||
Wait(0)
|
||||
end
|
||||
end,
|
||||
|
||||
HoverOut = function (self)
|
||||
if self.HoverObject == nil then return end
|
||||
if self.HoverObject and self.HoverObject ~= 0 then
|
||||
local tries = 0
|
||||
while DoesEntityExist(self.HoverObject) do
|
||||
SetEntityAsMissionEntity(self.HoverObject, true, true)
|
||||
DeleteEntity(self.HoverObject)
|
||||
Wait(50)
|
||||
tries = tries + 1
|
||||
if tries > 25 then
|
||||
break
|
||||
end
|
||||
end
|
||||
self.HoverObject = nil
|
||||
end
|
||||
self.IsHovering = false
|
||||
end,
|
||||
|
||||
SelectOwnedItem = function (self, data)
|
||||
self:StopPlacement()
|
||||
if data ~= nil then
|
||||
self:StartPlacement(data)
|
||||
end
|
||||
end,
|
||||
|
||||
RemoveOwnedItem = function (self, data)
|
||||
local item = data
|
||||
|
||||
if item ~= nil then
|
||||
SendNUIMessage({
|
||||
action = "removeOwnedItem",
|
||||
data = item
|
||||
})
|
||||
|
||||
TriggerServerEvent("ps-housing:server:removeFurniture", self.property_id, item.id)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
RegisterNUICallback("previewFurniture", function(data, cb)
|
||||
Modeler:StartPlacement(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("moveObject", function(data, cb)
|
||||
Modeler:MoveObject(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("rotateObject", function(data, cb)
|
||||
Modeler:RotateObject(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("stopPlacement", function(data, cb)
|
||||
Modeler:StopPlacement()
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("setObjectAlpha", function(data, cb)
|
||||
Modeler:SetObjectAlpha(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("hideUI", function(data, cb)
|
||||
Modeler:CloseMenu()
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("freecamMode", function(data, cb)
|
||||
Modeler:FreecamMode(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("placeOnGround", function(data, cb)
|
||||
local coords = Modeler:PlaceOnGround()
|
||||
cb(coords)
|
||||
end)
|
||||
|
||||
RegisterNUICallback("selectCartItem", function(data, cb)
|
||||
Modeler:SelectCartItem(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("addToCart", function(data, cb)
|
||||
Modeler:AddToCart(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("removeCartItem", function(data, cb)
|
||||
Modeler:RemoveFromCart(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("updateCartItem", function(data, cb)
|
||||
Modeler:UpdateCartItem(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("buyCartItems", function(data, cb)
|
||||
Modeler:BuyCart()
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("hoverIn", function(data, cb)
|
||||
Modeler:HoverIn(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("hoverOut", function(data, cb)
|
||||
Modeler:HoverOut()
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("setHoverDistance", function(data, cb)
|
||||
Modeler:SetHoverDistance(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("selectOwnedItem", function(data, cb)
|
||||
Modeler:SelectOwnedItem(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("removeOwnedItem", function(data, cb)
|
||||
Modeler:RemoveOwnedItem(data)
|
||||
cb("ok")
|
||||
end)
|
||||
|
||||
RegisterNUICallback("showNotification", function(data, cb)
|
||||
Framework[Config.Notify].Notify(data.message, data.type)
|
||||
cb("ok")
|
||||
end)
|
129
resources/[ps]/ps-housing/client/shell.lua
Normal file
129
resources/[ps]/ps-housing/client/shell.lua
Normal file
@ -0,0 +1,129 @@
|
||||
Shells = {}
|
||||
|
||||
|
||||
Shell = {
|
||||
entity = nil,
|
||||
hash = nil,
|
||||
position = nil,
|
||||
rotation = nil,
|
||||
shellData = nil,
|
||||
oldCoord = nil,
|
||||
|
||||
exitTarget = nil,
|
||||
}
|
||||
Shell.__index = Shell
|
||||
|
||||
|
||||
function Shell:SpawnShell(shellHash, position, rotation)
|
||||
lib.requestModel(shellHash)
|
||||
|
||||
local entity = CreateObjectNoOffset(shellHash, position.x, position.y, position.z, false, false, false)
|
||||
FreezeEntityPosition(entity, true)
|
||||
|
||||
SetEntityRotation(entity, rotation, 2, true)
|
||||
|
||||
SetModelAsNoLongerNeeded(shellHash)
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
function Shell:DespawnShell()
|
||||
if DoesEntityExist(self.entity) then
|
||||
DeleteEntity(self.entity)
|
||||
end
|
||||
|
||||
if self.exitTarget then
|
||||
Framework[Config.Target].RemoveTargetZone(self.exitTarget)
|
||||
end
|
||||
self = nil
|
||||
end
|
||||
|
||||
|
||||
function Shell:CreatePropertyShell(shellName, position, rotation)
|
||||
local self = setmetatable({}, Shell)
|
||||
|
||||
self.shellData = Config.Shells[shellName]
|
||||
self.hash = self.shellData.hash
|
||||
self.position = position
|
||||
self.rotation = rotation or vector3(0.0, 0.0, 0.0)
|
||||
|
||||
self.entity = self:SpawnShell(self.hash, self.position, self.rotation)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- example of how to use
|
||||
-- exports["ps-housing"]:CreateTempShell("Modern Hotel", GetEntityCoords(PlayerPedId()), GetEntityRotation(PlayerPedId()), function()
|
||||
-- Framework[Config.Notify].Notify("You left the shell", "error")
|
||||
-- local coords = GetEntityCoords(PlayerPedId())
|
||||
-- SetEntityCoordsNoOffset(PlayerPedId(), coords.x, coords.y, coords.z + 50.0, false, false, true)
|
||||
-- end)
|
||||
-- this is used as a constructor for third party scripts
|
||||
function Shell:CreateTempShell(shellName, position, rotation, leaveCb)
|
||||
local self = setmetatable({}, Shell)
|
||||
self.shellData = Config.Shells[shellName]
|
||||
self.hash = self.shellData.hash
|
||||
self.position = position
|
||||
self.rotation = rotation
|
||||
|
||||
|
||||
DoScreenFadeOut(250)
|
||||
Wait(250)
|
||||
|
||||
self.oldCoord = GetEntityCoords(PlayerPedId())
|
||||
|
||||
self.entity = self:SpawnShell(self.hash, self.position, self.rotation)
|
||||
|
||||
|
||||
local doorOffset = self.shellData.doorOffset
|
||||
local offset = GetOffsetFromEntityInWorldCoords(self.entity, doorOffset.x, doorOffset.y, doorOffset.z)
|
||||
|
||||
SetEntityCoordsNoOffset(cache.ped, offset.x, offset.y, offset.z, false, false, true)
|
||||
SetEntityHeading(cache.ped, self.shellData.doorOffset.h)
|
||||
|
||||
local coords = offset
|
||||
local size = vector3(1.0, self.shellData.doorOffset.width, 3.0)
|
||||
local heading = self.shellData.doorOffset.h
|
||||
|
||||
local function leave()
|
||||
DoScreenFadeOut(250)
|
||||
Wait(250)
|
||||
|
||||
SetEntityCoordsNoOffset(PlayerPedId(), self.oldCoord.x, self.oldCoord.y, self.oldCoord.z, false, false, true)
|
||||
|
||||
if leaveCb then
|
||||
leaveCb()
|
||||
end
|
||||
|
||||
self:DespawnShell()
|
||||
|
||||
Wait(250)
|
||||
DoScreenFadeIn(250)
|
||||
end
|
||||
|
||||
|
||||
self.exitTarget = Framework[Config.Target].AddDoorZoneInsideTempShell(coords, size, heading, leave)
|
||||
|
||||
Wait(250)
|
||||
DoScreenFadeIn(250)
|
||||
|
||||
Shells[self.entity] = self
|
||||
|
||||
return self.entity
|
||||
end
|
||||
|
||||
exports('CreateTempShell', function(shellName, position, rotation, leaveCb)
|
||||
return Shell:CreateTempShell(shellName, position, rotation, leaveCb)
|
||||
end)
|
||||
|
||||
exports("GetShellData", function (shellName)
|
||||
return Config.Shells[shellName]
|
||||
end)
|
||||
|
||||
exports("DespawnTempShell", function (shellEntity)
|
||||
if Shells[shellEntity] then
|
||||
Shells[shellEntity]:DespawnShell()
|
||||
end
|
||||
end)
|
15
resources/[ps]/ps-housing/docs/pull_request_template.md
Normal file
15
resources/[ps]/ps-housing/docs/pull_request_template.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Overview
|
||||
*Provide a brief overview of the purpose of this pull request*
|
||||
|
||||
# Details
|
||||
*Provide details on the change you are making, why they are necessary, and any information that will help with understanding.*
|
||||
|
||||
# UI Changes / Functionality
|
||||
*Include a before and after screenshot/recording if applicable. Short recordings showing that your changes work are helpful, but optional.*
|
||||
|
||||
# Testing Steps
|
||||
*Provide a list of repro steps on how to test that your changes are valid.*
|
||||
|
||||
- [ ] Did you test the changes you made?
|
||||
- [ ] Did you test core functionality of the script to ensure your changes do not regress other areas?
|
||||
- [ ] Did you test your changes in multiplayer to ensure it works correctly on all clients?
|
47
resources/[ps]/ps-housing/fxmanifest.lua
Normal file
47
resources/[ps]/ps-housing/fxmanifest.lua
Normal file
@ -0,0 +1,47 @@
|
||||
fx_version 'cerulean'
|
||||
|
||||
game "gta5"
|
||||
|
||||
author "Xirvin#0985 and Project Sloth"
|
||||
version '1.1.3'
|
||||
|
||||
repository 'Project-Sloth/ps-housing'
|
||||
|
||||
lua54 'yes'
|
||||
|
||||
ui_page 'html/index.html'
|
||||
|
||||
dependency 'fivem-freecam'
|
||||
|
||||
shared_script {
|
||||
'@ox_lib/init.lua',
|
||||
"shared/config.lua",
|
||||
"shared/framework.lua",
|
||||
}
|
||||
|
||||
client_script {
|
||||
'client/shell.lua',
|
||||
'client/apartment.lua',
|
||||
'client/cl_property.lua',
|
||||
'client/client.lua',
|
||||
'client/modeler.lua',
|
||||
'client/migrate.lua'
|
||||
}
|
||||
|
||||
server_script {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
"server/sv_property.lua",
|
||||
"server/server.lua",
|
||||
"server/migrate.lua"
|
||||
}
|
||||
|
||||
files {
|
||||
'html/**',
|
||||
'stream/starter_shells_k4mb1.ytyp'
|
||||
}
|
||||
|
||||
this_is_a_map 'yes'
|
||||
data_file 'DLC_ITYP_REQUEST' 'starter_shells_k4mb1.ytyp'
|
||||
|
||||
file 'stream/**.ytyp'
|
||||
data_file 'DLC_ITYP_REQUEST' 'stream/**.ytyp'
|
1
resources/[ps]/ps-housing/html/index.css
Normal file
1
resources/[ps]/ps-housing/html/index.css
Normal file
File diff suppressed because one or more lines are too long
29
resources/[ps]/ps-housing/html/index.html
Normal file
29
resources/[ps]/ps-housing/html/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- FontAwesome Icons Import -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css">
|
||||
<!-- Material Icons Import -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<!-- Material Design Icons Import -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css">
|
||||
<!-- Line Awesome Icons Import -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/line-awesome/dist/line-awesome/css/line-awesome.min.css">
|
||||
<!-- Boostrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.min.css">
|
||||
|
||||
<!-- Satoshi Font -->
|
||||
<link href="https://fonts.cdnfonts.com/css/satoshi" rel="stylesheet">
|
||||
|
||||
<title>ps-housing</title>
|
||||
<script type="module" crossorigin src="./index.js"></script>
|
||||
<link rel="stylesheet" href="./index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
3441
resources/[ps]/ps-housing/html/index.js
Normal file
3441
resources/[ps]/ps-housing/html/index.js
Normal file
File diff suppressed because one or more lines are too long
21
resources/[ps]/ps-housing/properties.sql
Normal file
21
resources/[ps]/ps-housing/properties.sql
Normal file
@ -0,0 +1,21 @@
|
||||
DROP table IF EXISTS `properties`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `properties` (
|
||||
`property_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`owner_citizenid` varchar(50) NULL,
|
||||
`street` VARCHAR(100) NULL,
|
||||
`region` VARCHAR(100) NULL,
|
||||
`description` LONGTEXT NULL,
|
||||
`has_access` JSON NULL DEFAULT (JSON_ARRAY()), -- [citizenid1, citizenid2, ...]
|
||||
`extra_imgs` JSON NULL DEFAULT (JSON_ARRAY()),
|
||||
`furnitures` JSON NULL DEFAULT (JSON_ARRAY()),
|
||||
`for_sale` boolean NOT NULL DEFAULT 1,
|
||||
`price` int(11) NOT NULL DEFAULT 0,
|
||||
`shell` varchar(50) NOT NULL,
|
||||
`apartment` varchar(50) NULL DEFAULT NULL, -- if NULL then it's a house
|
||||
`door_data` JSON NULL DEFAULT NULL, -- {"x": 0.0, "y": 0.0, "z": 0.0, "h": 0.0, "length": 0.0, "width": 0.0}
|
||||
`garage_data` JSON NULL DEFAULT NULL, -- {"x": 0.0, "y": 0.0, "z": 0.0} -- NULL if no garage
|
||||
PRIMARY KEY (`property_id`),
|
||||
CONSTRAINT `FK_owner_citizenid` FOREIGN KEY (`owner_citizenid`) REFERENCES `players` (`citizenid`) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT `UQ_owner_apartment` UNIQUE (`owner_citizenid`, `apartment`) -- A character can only own one apartment
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
25
resources/[ps]/ps-housing/server/migrate.lua
Normal file
25
resources/[ps]/ps-housing/server/migrate.lua
Normal file
@ -0,0 +1,25 @@
|
||||
local AptNames = {
|
||||
['apartment1'] = "South Rockford Drive",
|
||||
["apartment2"] = "Morningwood Blvd",
|
||||
["apartment3"] = "Integrity Way",
|
||||
["apartment4"] = "Tinsel Towers",
|
||||
["apartment5"] = "Fantastic Plaza",
|
||||
}
|
||||
|
||||
local Shells = {
|
||||
[1] = `shell_v16low`,
|
||||
[2] = `shell_v16mid`,
|
||||
[3] = `shell_trevor`,
|
||||
[4] = `shell_trailer`,
|
||||
[5] = `shell_lester`,
|
||||
[6] = `shell_ranch`,
|
||||
[7] = `container_shell`,
|
||||
[8] = `furnitured_midapart`,
|
||||
[9] = `modernhotel_shell`,
|
||||
[10] = `shell_frankaunt`,
|
||||
[11] = `shell_garagem`,
|
||||
[12] = `shell_michael`,
|
||||
[13] = `shell_office1`,
|
||||
[14] = `shell_store1`,
|
||||
[15] = `shell_warehouse1`
|
||||
}
|
268
resources/[ps]/ps-housing/server/server.lua
Normal file
268
resources/[ps]/ps-housing/server/server.lua
Normal file
@ -0,0 +1,268 @@
|
||||
QBCore = exports['qb-core']:GetCoreObject()
|
||||
-- PSCore = exports['ps-core']:GetCoreObject()
|
||||
|
||||
local dbloaded = false
|
||||
MySQL.ready(function()
|
||||
MySQL.query('SELECT * FROM properties', {}, function(result)
|
||||
if not result then return end
|
||||
if result.id then -- If only one result
|
||||
result = {result}
|
||||
end
|
||||
for _, v in pairs(result) do
|
||||
local id = tostring(v.property_id)
|
||||
local propertyData = {
|
||||
property_id = tostring(id),
|
||||
owner = v.owner_citizenid,
|
||||
street = v.street,
|
||||
region = v.region,
|
||||
description = v.description,
|
||||
has_access = json.decode(v.has_access),
|
||||
extra_imgs = json.decode(v.extra_imgs),
|
||||
furnitures = json.decode(v.furnitures),
|
||||
for_sale = v.for_sale,
|
||||
price = v.price,
|
||||
shell = v.shell,
|
||||
apartment = v.apartment,
|
||||
door_data = json.decode(v.door_data),
|
||||
garage_data = json.decode(v.garage_data),
|
||||
}
|
||||
PropertiesTable[id] = Property:new(propertyData)
|
||||
end
|
||||
|
||||
dbloaded = true
|
||||
end)
|
||||
end)
|
||||
|
||||
lib.callback.register("ps-housing:server:requestProperties", function(source)
|
||||
while not dbloaded do
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
return PropertiesTable
|
||||
end)
|
||||
|
||||
AddEventHandler("ps-housing:server:registerProperty", function (propertyData) -- triggered by realtor job
|
||||
local propertyData = propertyData
|
||||
|
||||
propertyData.owner = propertyData.owner or nil
|
||||
propertyData.has_access = propertyData.has_access or {}
|
||||
propertyData.extra_imgs = propertyData.extra_imgs or {}
|
||||
propertyData.furnitures = propertyData.furnitures or {}
|
||||
propertyData.door_data = propertyData.door_data or {}
|
||||
propertyData.garage_data = propertyData.garage_data or {}
|
||||
|
||||
local cols = "(owner_citizenid, street, region, description, has_access, extra_imgs, furnitures, for_sale, price, shell, apartment, door_data, garage_data)"
|
||||
local vals = "(@owner_citizenid, @street, @region, @description, @has_access, @extra_imgs, @furnitures, @for_sale, @price, @shell, @apartment, @door_data, @garage_data)"
|
||||
|
||||
local id = MySQL.insert.await("INSERT INTO properties " .. cols .. " VALUES " .. vals , {
|
||||
["@owner_citizenid"] = propertyData.owner or nil,
|
||||
["@street"] = propertyData.street,
|
||||
["@region"] = propertyData.region,
|
||||
["@description"] = propertyData.description,
|
||||
["@has_access"] = json.encode(propertyData.has_access),
|
||||
["@extra_imgs"] = json.encode(propertyData.extra_imgs),
|
||||
["@furnitures"] = json.encode(propertyData.furnitures),
|
||||
["@for_sale"] = propertyData.for_sale ~= nil and propertyData.for_sale or 1,
|
||||
["@price"] = propertyData.price or 0,
|
||||
["@shell"] = propertyData.shell,
|
||||
["@apartment"] = propertyData.apartment,
|
||||
["@door_data"] = json.encode(propertyData.door_data),
|
||||
["@garage_data"] = json.encode(propertyData.garage_data),
|
||||
})
|
||||
id = tostring(id)
|
||||
propertyData.property_id = id
|
||||
PropertiesTable[id] = Property:new(propertyData)
|
||||
|
||||
TriggerClientEvent("ps-housing:client:addProperty", -1, propertyData)
|
||||
|
||||
if propertyData.apartment then
|
||||
local player = QBCore.Functions.GetPlayerByCitizenId(propertyData.owner)
|
||||
local src = player.PlayerData.source
|
||||
|
||||
local property = Property.Get(id)
|
||||
property:PlayerEnter(src)
|
||||
|
||||
Wait(1000)
|
||||
|
||||
local query = "SELECT skin FROM playerskins WHERE citizenid = ?"
|
||||
local result = MySQL.Sync.fetchAll(query, {propertyData.owner})
|
||||
|
||||
if result and result[1] then
|
||||
Debug("Player: " .. propertyData.owner .. " skin already exists!")
|
||||
else
|
||||
TriggerClientEvent("hp_charcreator:openCreator", src)
|
||||
-- TriggerClientEvent("qb-clothes:client:CreateFirstCharacter", src)
|
||||
Debug("Player: " .. propertyData.owner .. " is creating a new character!")
|
||||
end
|
||||
|
||||
Framework[Config.Notify].Notify(src, "Open radial menu for furniture menu and place down your stash and clothing locker.", "info")
|
||||
|
||||
-- This will create the stash for the apartment and migrate the items from the old apartment stash if applicable
|
||||
TriggerEvent("ps-housing:server:createApartmentStash", propertyData.owner, id)
|
||||
end
|
||||
end)
|
||||
|
||||
lib.callback.register("ps-housing:cb:GetOwnedApartment", function(source, cid)
|
||||
Debug("ps-housing:cb:GetOwnedApartment", source, cid)
|
||||
if cid ~= nil then
|
||||
local result = MySQL.query.await('SELECT * FROM properties WHERE owner_citizenid = ? AND apartment IS NOT NULL AND apartment <> ""', { cid })
|
||||
if result[1] ~= nil then
|
||||
return result[1]
|
||||
end
|
||||
return nil
|
||||
else
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local result = MySQL.query.await('SELECT * FROM properties WHERE owner_citizenid = ? AND apartment IS NOT NULL AND apartment <> ""', { Player.PlayerData.citizenid })
|
||||
if result[1] ~= nil then
|
||||
return result[1]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("ps-housing:server:updateProperty", function(type, property_id, data)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
property[type](property, data)
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStart", function(resourceName) -- Used for when the resource is restarted while in game
|
||||
if (GetCurrentResourceName() == resourceName) then
|
||||
while not dbloaded do
|
||||
Wait(100)
|
||||
end
|
||||
TriggerClientEvent('ps-housing:client:initialiseProperties', -1, PropertiesTable)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:server:createNewApartment", function(aptLabel)
|
||||
local src = source
|
||||
if not Config.StartingApartment then return end
|
||||
local citizenid = GetCitizenid(src)
|
||||
local PlayerData = GetPlayerData(src)
|
||||
|
||||
local apartment = Config.Apartments[aptLabel]
|
||||
if not apartment then return end
|
||||
|
||||
local propertyData = {
|
||||
owner = citizenid,
|
||||
description = string.format("Dette er %s's lejlighed ved %s", PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname, apartment.label),
|
||||
for_sale = 0,
|
||||
shell = apartment.shell,
|
||||
apartment = apartment.label,
|
||||
}
|
||||
|
||||
Debug("Creating new apartment for " .. GetPlayerName(src) .. " in " .. apartment.label)
|
||||
|
||||
Framework[Config.Logs].SendLog("Creating new apartment for " .. GetPlayerName(src) .. " in " .. apartment.label)
|
||||
|
||||
TriggerEvent("ps-housing:server:registerProperty", propertyData)
|
||||
end)
|
||||
|
||||
-- Creates apartment stash
|
||||
-- If player has an existing apartment from qb-apartments, it will transfer the items over to the new apartment stash
|
||||
RegisterNetEvent("ps-housing:server:createApartmentStash", function(citizenId, propertyId)
|
||||
local stashId = string.format("property_%s", propertyId)
|
||||
|
||||
-- Check for existing apartment and corresponding stash
|
||||
local result = MySQL.query.await('SELECT items, stash FROM stashitems WHERE stash IN (SELECT name FROM apartments WHERE citizenid = ?)', { citizenId })
|
||||
|
||||
local items = {}
|
||||
if result[1] ~= nil then
|
||||
items = json.decode(result[1].items)
|
||||
|
||||
-- Delete the old apartment stash as it is no longer needed
|
||||
MySQL.Async.execute('DELETE FROM stashitems WHERE stash = ?', { result[1].stash })
|
||||
end
|
||||
|
||||
-- This will create the stash for the apartment (without requiring player to have first opened and placed item in it)
|
||||
TriggerEvent('ps-inventory:server:SaveStashItems', stashId, items)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('qb-apartments:returnBucket', function()
|
||||
local src = source
|
||||
SetPlayerRoutingBucket(src, 0)
|
||||
end)
|
||||
|
||||
AddEventHandler("ps-housing:server:addTenantToApartment", function (data)
|
||||
local apartment = data.apartment
|
||||
local targetSrc = tonumber(data.targetSrc)
|
||||
local realtorSrc = data.realtorSrc
|
||||
local targetCitizenid = GetCitizenid(targetSrc, realtorSrc)
|
||||
|
||||
-- id of current apartment so we can change it
|
||||
local property_id = nil
|
||||
|
||||
for _, v in pairs(PropertiesTable) do
|
||||
local propertyData = v.propertyData
|
||||
if propertyData.owner == targetCitizenid then
|
||||
if propertyData.apartment == apartment then
|
||||
Framework[Config.Notify].Notify(targetSrc, "Du er allerede i lejligheden", "error")
|
||||
Framework[Config.Notify].Notify(targetSrc, "Denne person er allerede i lejligheden", "error")
|
||||
|
||||
return
|
||||
elseif #propertyData.apartment > 1 then
|
||||
property_id = propertyData.property_id
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
property:UpdateApartment(data)
|
||||
|
||||
local citizenid = GetCitizenid(targetSrc, realtorSrc)
|
||||
local targetToAdd = QBCore.Functions.GetPlayerByCitizenId(citizenid)
|
||||
local targetPlayer = targetToAdd.PlayerData
|
||||
|
||||
Framework[Config.Notify].Notify(targetSrc, "Din lejlighed er nu på "..apartment, "success")
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Du har tilføjet ".. targetPlayer.charinfo.firstname .. " " .. targetPlayer.charinfo.lastname .. " til lejligheden "..apartment, "success")
|
||||
end)
|
||||
|
||||
exports('IsOwner', function(src, property_id)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return false end
|
||||
|
||||
local citizenid = GetCitizenid(src, src)
|
||||
return property:CheckForAccess(citizenid)
|
||||
end)
|
||||
|
||||
function GetCitizenid(targetSrc, callerSrc)
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(targetSrc))
|
||||
if not Player then
|
||||
Framework[Config.Notify].Notify(callerSrc, "Person ikke fundet.", "error")
|
||||
return
|
||||
end
|
||||
local PlayerData = Player.PlayerData
|
||||
local citizenid = PlayerData.citizenid
|
||||
return citizenid
|
||||
end
|
||||
|
||||
function GetCharName(src)
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(src))
|
||||
if not Player then return end
|
||||
local PlayerData = Player.PlayerData
|
||||
return PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname
|
||||
end
|
||||
|
||||
function GetPlayerData(src)
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(src))
|
||||
if not Player then return end
|
||||
local PlayerData = Player.PlayerData
|
||||
return PlayerData
|
||||
end
|
||||
|
||||
function GetPlayer(src)
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(src))
|
||||
if not Player then return end
|
||||
return Player
|
||||
end
|
||||
|
||||
-- if PSCore then
|
||||
-- PSCore.Functions.CheckForUpdates()
|
||||
-- PSCore.Functions.CheckResourceName()
|
||||
-- end
|
855
resources/[ps]/ps-housing/server/sv_property.lua
Normal file
855
resources/[ps]/ps-housing/server/sv_property.lua
Normal file
@ -0,0 +1,855 @@
|
||||
Property = {
|
||||
property_id = nil,
|
||||
propertyData = nil,
|
||||
playersInside = {}, -- src
|
||||
playersDoorbell = {}, -- src
|
||||
|
||||
raiding = false,
|
||||
}
|
||||
Property.__index = Property
|
||||
|
||||
function Property:new(propertyData)
|
||||
local self = setmetatable({}, Property)
|
||||
|
||||
self.property_id = tostring(propertyData.property_id)
|
||||
self.propertyData = propertyData
|
||||
|
||||
local stashName = string.format("property_%s", propertyData.property_id)
|
||||
local stashConfig = Config.Shells[propertyData.shell].stash
|
||||
|
||||
Framework[Config.Inventory].RegisterInventory(stashName, propertyData.street or propertyData.apartment or stashName, stashConfig)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Property:PlayerEnter(src)
|
||||
local _src = tostring(src)
|
||||
self.playersInside[_src] = true
|
||||
|
||||
TriggerClientEvent('qb-weathersync:client:DisableSync', src)
|
||||
TriggerClientEvent('ps-housing:client:enterProperty', src, self.property_id)
|
||||
|
||||
if next(self.playersDoorbell) then
|
||||
TriggerClientEvent("ps-housing:client:updateDoorbellPool", src, self.property_id, self.playersDoorbell)
|
||||
if self.playersDoorbell[_src] then
|
||||
self.playersDoorbell[_src] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
|
||||
if self:CheckForAccess(citizenid) then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local insideMeta = Player.PlayerData.metadata["inside"]
|
||||
|
||||
insideMeta.property_id = self.property_id
|
||||
Player.Functions.SetMetaData("inside", insideMeta)
|
||||
end
|
||||
|
||||
local bucket = tonumber(self.property_id) -- because the property_id is a string
|
||||
SetPlayerRoutingBucket(src, bucket)
|
||||
end
|
||||
|
||||
function Property:PlayerLeave(src)
|
||||
local _src = tostring(src)
|
||||
self.playersInside[_src] = nil
|
||||
|
||||
TriggerClientEvent('qb-weathersync:client:EnableSync', src)
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
|
||||
if self:CheckForAccess(citizenid) then
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local insideMeta = Player.PlayerData.metadata["inside"]
|
||||
|
||||
insideMeta.property_id = nil
|
||||
Player.Functions.SetMetaData("inside", insideMeta)
|
||||
end
|
||||
|
||||
SetPlayerRoutingBucket(src, 0)
|
||||
end
|
||||
|
||||
function Property:CheckForAccess(citizenid)
|
||||
if self.propertyData.owner == citizenid then return true end
|
||||
return lib.table.contains(self.propertyData.has_access, citizenid)
|
||||
end
|
||||
|
||||
function Property:AddToDoorbellPoolTemp(src)
|
||||
local _src = tostring(src)
|
||||
|
||||
local name = GetCharName(src)
|
||||
|
||||
self.playersDoorbell[_src] = {
|
||||
src = src,
|
||||
name = name
|
||||
}
|
||||
|
||||
for src, _ in pairs(self.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
|
||||
Framework[Config.Notify].Notify(targetSrc, "Der er gæster ved døren.", "info")
|
||||
TriggerClientEvent("ps-housing:client:updateDoorbellPool", targetSrc, self.property_id, self.playersDoorbell)
|
||||
end
|
||||
|
||||
Framework[Config.Notify].Notify(src, "Du har ringet på dørklokken...", "info")
|
||||
|
||||
SetTimeout(10000, function()
|
||||
if self.playersDoorbell[_src] then
|
||||
self.playersDoorbell[_src] = nil
|
||||
Framework[Config.Notify].Notify(src, "Ingen åbnede døren.", "error")
|
||||
end
|
||||
|
||||
for src, _ in pairs(self.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateDoorbellPool", targetSrc, self.property_id, self.playersDoorbell)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Property:RemoveFromDoorbellPool(src)
|
||||
local _src = tostring(src)
|
||||
|
||||
if self.playersDoorbell[_src] then
|
||||
self.playersDoorbell[_src] = nil
|
||||
end
|
||||
|
||||
for src, _ in pairs(self.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateDoorbellPool", targetSrc, self.property_id, self.playersDoorbell)
|
||||
end
|
||||
end
|
||||
|
||||
function Property:StartRaid()
|
||||
self.raiding = true
|
||||
|
||||
for src, _ in pairs(self.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
Framework[Config.Notify].Notify(targetSrc, "Denne ejendom bliver ransaget.", "error")
|
||||
end
|
||||
|
||||
SetTimeout(Config.RaidTimer * 60000, function()
|
||||
self.raiding = false
|
||||
end)
|
||||
end
|
||||
|
||||
function Property:UpdateFurnitures(furnitures)
|
||||
self.propertyData.furnitures = furnitures
|
||||
|
||||
MySQL.update("UPDATE properties SET furnitures = @furnitures WHERE property_id = @property_id", {
|
||||
["@furnitures"] = json.encode(furnitures),
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
for src, _ in pairs(self.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
TriggerClientEvent("ps-housing:client:updateFurniture", targetSrc, self.property_id, furnitures)
|
||||
end
|
||||
end
|
||||
|
||||
function Property:UpdateDescription(data)
|
||||
local description = data.description
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
if self.propertyData.description == description then return end
|
||||
|
||||
self.propertyData.description = description
|
||||
|
||||
MySQL.update("UPDATE properties SET description = @description WHERE property_id = @property_id", {
|
||||
["@description"] = description,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateDescription", self.property_id, description)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Description** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Description of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdatePrice(data)
|
||||
local price = data.price
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
if self.propertyData.price == price then return end
|
||||
|
||||
self.propertyData.price = price
|
||||
|
||||
MySQL.update("UPDATE properties SET price = @price WHERE property_id = @property_id", {
|
||||
["@price"] = price,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdatePrice", self.property_id, price)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Price** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Price of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdateForSale(data)
|
||||
local forsale = data.forsale
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
self.propertyData.for_sale = forsale
|
||||
|
||||
MySQL.update("UPDATE properties SET for_sale = @for_sale WHERE property_id = @property_id", {
|
||||
["@for_sale"] = forsale and 1 or 0,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateForSale", self.property_id, forsale)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed For Sale** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed For Sale of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdateShell(data)
|
||||
local shell = data.shell
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
if self.propertyData.shell == shell then return end
|
||||
|
||||
self.propertyData.shell = shell
|
||||
|
||||
MySQL.update("UPDATE properties SET shell = @shell WHERE property_id = @property_id", {
|
||||
["@shell"] = shell,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateShell", self.property_id, shell)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Shell** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Shell of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdateOwner(data)
|
||||
local targetSrc = data.targetSrc
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
if not realtorSrc then Debug("No Realtor Src found") return end
|
||||
if not targetSrc then Debug("No Target Src found") return end
|
||||
|
||||
local previousOwner = self.propertyData.owner
|
||||
|
||||
local targetPlayer = QBCore.Functions.GetPlayer(tonumber(targetSrc))
|
||||
|
||||
local PlayerData = targetPlayer.PlayerData
|
||||
local bank = PlayerData.money.bank
|
||||
local citizenid = PlayerData.citizenid
|
||||
|
||||
if self.propertyData.owner == citizenid then
|
||||
Framework[Config.Notify].Notify(targetSrc, "Du ejer allerede denne ejendom", "error")
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Kunden ejer allerede denne ejendom", "error")
|
||||
return
|
||||
end
|
||||
|
||||
--add callback
|
||||
local targetAllow = lib.callback.await("ps-housing:cb:confirmPurchase", targetSrc, self.propertyData.price, self.propertyData.street, self.propertyData.property_id)
|
||||
|
||||
if targetAllow ~= "confirm" then
|
||||
Framework[Config.Notify].Notify(targetSrc, "Du bekræftede ikke købet", "info")
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Kunden bekræftede ikke købet", "error")
|
||||
return
|
||||
end
|
||||
|
||||
if bank < self.propertyData.price then
|
||||
Framework[Config.Notify].Notify(targetSrc, "Du har ikke nok penge i banken", "error")
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Kunden har ikke nok penge i banken", "error")
|
||||
return
|
||||
end
|
||||
|
||||
targetPlayer.Functions.RemoveMoney('bank', self.propertyData.price, "Købte ejendom: " .. self.propertyData.street .. " " .. self.property_id)
|
||||
|
||||
local prevPlayer = QBCore.Functions.GetPlayerByCitizenId(previousOwner)
|
||||
local realtor = QBCore.Functions.GetPlayer(tonumber(realtorSrc))
|
||||
local realtorGradeLevel = realtor.PlayerData.job.grade.level
|
||||
|
||||
local commission = math.floor(self.propertyData.price * Config.Commissions[realtorGradeLevel])
|
||||
|
||||
local totalAfterCommission = self.propertyData.price - commission
|
||||
|
||||
if prevPlayer ~= nil then
|
||||
Framework[Config.Notify].Notify(prevPlayer.PlayerData.source, "Ejendom solgt: " .. self.propertyData.street .. " " .. self.property_id, "success")
|
||||
prevPlayer.Functions.AddMoney('bank', totalAfterCommission, "Solgte ejendom: " .. self.propertyData.street .. " " .. self.property_id)
|
||||
elseif previousOwner then
|
||||
MySQL.Async.execute('UPDATE `players` SET `bank` = `bank` + @price WHERE `citizenid` = @citizenid', {
|
||||
['@citizenid'] = previousOwner,
|
||||
['@price'] = totalAfterCommission
|
||||
})
|
||||
end
|
||||
|
||||
realtor.Functions.AddMoney('bank', commission, "Kommission fra ejendom: " .. self.propertyData.street .. " " .. self.property_id)
|
||||
|
||||
self.propertyData.owner = citizenid
|
||||
|
||||
MySQL.update("UPDATE properties SET owner_citizenid = @owner_citizenid, for_sale = @for_sale WHERE property_id = @property_id", {
|
||||
["@owner_citizenid"] = citizenid,
|
||||
["@for_sale"] = 0,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
self.propertyData.furnitures = {} -- to be fetched on enter
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateOwner", self.property_id, citizenid)
|
||||
|
||||
Framework[Config.Logs].SendLog("**House Bought** by: **"..PlayerData.charinfo.firstname.." "..PlayerData.charinfo.lastname.."** for $"..self.propertyData.price.." from **"..realtor.PlayerData.charinfo.firstname.." "..realtor.PlayerData.charinfo.lastname.."** !")
|
||||
|
||||
Framework[Config.Notify].Notify(targetSrc, "Du har købt ejendommen for "..self.propertyData.price..",-", "success")
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Kunden har købt ejendommen for "..self.propertyData.price..",-", "success")
|
||||
end
|
||||
|
||||
function Property:UpdateImgs(data)
|
||||
local imgs = data.imgs
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
self.propertyData.imgs = imgs
|
||||
|
||||
MySQL.update("UPDATE properties SET extra_imgs = @extra_imgs WHERE property_id = @property_id", {
|
||||
["@extra_imgs"] = json.encode(imgs),
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateImgs", self.property_id, imgs)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Images** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Imgs of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
|
||||
function Property:UpdateDoor(data)
|
||||
local door = data.door
|
||||
|
||||
if not door then return end
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
local newDoor = {
|
||||
x = math.floor(door.x * 10000) / 10000,
|
||||
y = math.floor(door.y * 10000) / 10000,
|
||||
z = math.floor(door.z * 10000) / 10000,
|
||||
h = math.floor(door.h * 10000) / 10000,
|
||||
length = door.length or 1.5,
|
||||
width = door.width or 2.2,
|
||||
locked = door.locked or false,
|
||||
}
|
||||
|
||||
self.propertyData.door_data = newDoor
|
||||
|
||||
self.propertyData.street = data.street
|
||||
self.propertyData.region = data.region
|
||||
|
||||
|
||||
MySQL.update("UPDATE properties SET door_data = @door, street = @street, region = @region WHERE property_id = @property_id", {
|
||||
["@door"] = json.encode(newDoor),
|
||||
["@property_id"] = self.property_id,
|
||||
["@street"] = data.street,
|
||||
["@region"] = data.region
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateDoor", self.property_id, newDoor, data.street, data.region)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Door** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Door of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdateHas_access(data)
|
||||
local has_access = data or {}
|
||||
|
||||
self.propertyData.has_access = has_access
|
||||
|
||||
MySQL.update("UPDATE properties SET has_access = @has_access WHERE property_id = @property_id", {
|
||||
["@has_access"] = json.encode(has_access), --Array of cids
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateHas_access", self.property_id, has_access)
|
||||
|
||||
Debug("Changed Has Access of property with id: " .. self.property_id)
|
||||
end
|
||||
|
||||
function Property:UpdateGarage(data)
|
||||
local garage = data.garage
|
||||
local realtorSrc = data.realtorSrc
|
||||
|
||||
local newData = {}
|
||||
|
||||
if data ~= nil then
|
||||
newData = {
|
||||
x = math.floor(garage.x * 10000) / 10000,
|
||||
y = math.floor(garage.y * 10000) / 10000,
|
||||
z = math.floor(garage.z * 10000) / 10000,
|
||||
h = math.floor(garage.h * 10000) / 10000,
|
||||
length = garage.length or 3.0,
|
||||
width = garage.width or 5.0,
|
||||
}
|
||||
end
|
||||
|
||||
self.propertyData.garage_data = newData
|
||||
|
||||
MySQL.update("UPDATE properties SET garage_data = @garageCoords WHERE property_id = @property_id", {
|
||||
["@garageCoords"] = json.encode(newData),
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateGarage", self.property_id, garage)
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Garage** of property with id: " .. self.property_id .. " by: " .. GetPlayerName(realtorSrc))
|
||||
|
||||
Debug("Changed Garage of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:UpdateApartment(data)
|
||||
local apartment = data.apartment
|
||||
local realtorSrc = data.realtorSrc
|
||||
local targetSrc = data.targetSrc
|
||||
|
||||
self.propertyData.apartment = apartment
|
||||
|
||||
MySQL.update("UPDATE properties SET apartment = @apartment WHERE property_id = @property_id", {
|
||||
["@apartment"] = apartment,
|
||||
["@property_id"] = self.property_id
|
||||
})
|
||||
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Ændrede lejligheden for ejendommen med ID: " .. self.property_id .." til ".. apartment, "success")
|
||||
|
||||
Framework[Config.Notify].Notify(targetSrc, "Ændrede lejlighed til " .. apartment, "success")
|
||||
|
||||
Framework[Config.Logs].SendLog("**Changed Apartment** with id: " .. self.property_id .. " by: **" .. GetPlayerName(realtorSrc) .. "** for **" .. GetPlayerName(targetSrc) .."**")
|
||||
|
||||
TriggerClientEvent("ps-housing:client:updateProperty", -1, "UpdateApartment", self.property_id, apartment)
|
||||
|
||||
Debug("Changed Apartment of property with id: " .. self.property_id, "by: " .. GetPlayerName(realtorSrc))
|
||||
end
|
||||
|
||||
function Property:DeleteProperty(data)
|
||||
local realtorSrc = data.realtorSrc
|
||||
local propertyid = self.property_id
|
||||
local realtorName = GetPlayerName(realtorSrc)
|
||||
|
||||
MySQL.Async.execute("DELETE FROM properties WHERE property_id = @property_id", {
|
||||
["@property_id"] = propertyid
|
||||
}, function (rowsChanged)
|
||||
if rowsChanged > 0 then
|
||||
Debug("Deleted property with id: " .. propertyid, "by: " .. realtorName)
|
||||
end
|
||||
end)
|
||||
|
||||
TriggerClientEvent("ps-housing:client:removeProperty", -1, propertyid)
|
||||
|
||||
Framework[Config.Notify].Notify(realtorSrc, "Ejendommen med id: " .. propertyid .." blev fjernet.", "info")
|
||||
|
||||
Framework[Config.Logs].SendLog("**Property Deleted** with id: " .. propertyid .. " by: " .. realtorName)
|
||||
|
||||
PropertiesTable[propertyid] = nil
|
||||
self = nil
|
||||
|
||||
Debug("Deleted property with id: " .. propertyid, "by: " .. realtorName)
|
||||
end
|
||||
|
||||
function Property.Get(property_id)
|
||||
return PropertiesTable[tostring(property_id)]
|
||||
end
|
||||
|
||||
RegisterNetEvent('ps-housing:server:enterProperty', function (property_id)
|
||||
local src = source
|
||||
Debug("Player is trying to enter property", property_id)
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then
|
||||
Debug("Properties returned", json.encode(PropertiesTable, {indent = true}))
|
||||
return
|
||||
end
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
|
||||
if property:CheckForAccess(citizenid) then
|
||||
Debug("Player has access to property")
|
||||
property:PlayerEnter(src)
|
||||
Debug("Player entered property")
|
||||
return
|
||||
end
|
||||
|
||||
local ringDoorbellConfirmation = lib.callback.await('ps-housing:cb:ringDoorbell', src)
|
||||
if ringDoorbellConfirmation == "confirm" then
|
||||
property:AddToDoorbellPoolTemp(src)
|
||||
Debug("Ringing doorbell")
|
||||
return
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:server:showcaseProperty", function(property_id)
|
||||
local src = source
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then
|
||||
Debug("Properties returned", json.encode(PropertiesTable, {indent = true}))
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local PlayerData = GetPlayerData(src)
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local onDuty = job.onduty
|
||||
|
||||
if jobName == Config.RealtorJobName and onDuty then
|
||||
local showcase = lib.callback.await('ps-housing:cb:showcase', src)
|
||||
if showcase == "confirm" then
|
||||
property:PlayerEnter(src)
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('ps-housing:server:raidProperty', function(property_id)
|
||||
local src = source
|
||||
Debug("Player is trying to raid property", property_id)
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then
|
||||
Debug("Properties returned", json.encode(PropertiesTable, {indent = true}))
|
||||
return
|
||||
end
|
||||
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
if not Player then return end
|
||||
local PlayerData = Player.PlayerData
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local gradeAllowed = tonumber(job.grade.level) >= Config.MinGradeToRaid
|
||||
local onDuty = job.onduty
|
||||
local raidItem = Config.RaidItem
|
||||
|
||||
-- Check if the police officer has the "stormram" item
|
||||
local hasStormRam = (Config.Inventory == "ox" and exports.ox_inventory:Search(src, "count", raidItem) > 0) or Player.Functions.GetItemByName(raidItem)
|
||||
|
||||
local isAllowedToRaid = PoliceJobs[jobName] and onDuty and gradeAllowed
|
||||
if isAllowedToRaid then
|
||||
if hasStormRam then
|
||||
if not property.raiding then
|
||||
local confirmRaid = lib.callback.await('ps-housing:cb:confirmRaid', src, (property.propertyData.street or property.propertyData.apartment) .. " " .. property.property_id, property_id)
|
||||
if confirmRaid == "confirm" then
|
||||
property:StartRaid(src)
|
||||
property:PlayerEnter(src)
|
||||
Framework[Config.Notify].Notify(src, "Ransagning startet", "success")
|
||||
|
||||
if Config.ConsumeRaidItem then
|
||||
-- Remove the "stormram" item from the officer's inventory
|
||||
if Config.Inventory == 'ox' then
|
||||
exports.ox_inventory:RemoveItem(src, raidItem, 1)
|
||||
else
|
||||
Player.Functions.RemoveItem(raidItem, 1)
|
||||
TriggerClientEvent("inventory:client:ItemBox", src, QBCore.Shared.Items[raidItem], "remove")
|
||||
TriggerEvent("inventory:server:RemoveItem", src, raidItem, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
Framework[Config.Notify].Notify(src, "Ransagning igang", "success")
|
||||
property:PlayerEnter(src)
|
||||
end
|
||||
else
|
||||
Framework[Config.Notify].Notify(src, "Du skal bruge en rambuk for at starte en ransagning", "error")
|
||||
end
|
||||
else
|
||||
if not PoliceJobs[jobName] then
|
||||
Framework[Config.Notify].Notify(src, "Kun politiet kan starte ransagninger", "error")
|
||||
elseif not onDuty then
|
||||
Framework[Config.Notify].Notify(src, "Du skal være på arbejde for at kunne starte en ransagning", "error")
|
||||
elseif not gradeAllowed then
|
||||
Framework[Config.Notify].Notify(src, "Du har ikke høj nok stilling, til at kunne starte en ransagnings", "error")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
|
||||
lib.callback.register('ps-housing:cb:getFurnitures', function(source, property_id)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
return property.propertyData.furnitures or {}
|
||||
end)
|
||||
|
||||
|
||||
lib.callback.register('ps-housing:cb:getPlayersInProperty', function(source, property_id)
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
local players = {}
|
||||
|
||||
for src, _ in pairs(property.playersInside) do
|
||||
local targetSrc = tonumber(src)
|
||||
if targetSrc ~= source then
|
||||
local name = GetCharName(targetSrc)
|
||||
|
||||
players[#players + 1] = {
|
||||
src = targetSrc,
|
||||
name = name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return players or {}
|
||||
end)
|
||||
|
||||
RegisterNetEvent('ps-housing:server:leaveProperty', function (property_id)
|
||||
local src = source
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then return end
|
||||
|
||||
property:PlayerLeave(src)
|
||||
end)
|
||||
|
||||
-- When player presses doorbell, owner can let them in and this is what is triggered
|
||||
RegisterNetEvent("ps-housing:server:doorbellAnswer", function (data)
|
||||
local src = source
|
||||
local targetSrc = data.targetSrc
|
||||
|
||||
local property = Property.Get(data.property_id)
|
||||
if not property then return end
|
||||
|
||||
if not property.playersInside[tostring(src)] then return end
|
||||
property:RemoveFromDoorbellPool(targetSrc)
|
||||
|
||||
property:PlayerEnter(targetSrc)
|
||||
end)
|
||||
|
||||
--@@ NEED TO REDO THIS DOG SHIT
|
||||
-- I think its not bad anymore but if u got a better idea lmk
|
||||
RegisterNetEvent("ps-housing:server:buyFurniture", function(property_id, items, price)
|
||||
local src = source
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
local PlayerData = GetPlayerData(src)
|
||||
local Player = GetPlayer(src)
|
||||
-- ik ik, i cbf rn. if your reading this and its still shit, tell me
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
if not property:CheckForAccess(citizenid) then return end
|
||||
|
||||
local price = tonumber(price)
|
||||
|
||||
if price > PlayerData.money.bank and price > PlayerData.money.cash then
|
||||
Framework[Config.Notify].Notify(src, "Du har ikke råd!", "error")
|
||||
return
|
||||
end
|
||||
|
||||
if price <= PlayerData.money.cash then
|
||||
Player.Functions.RemoveMoney('cash', price, "Købte møbler")
|
||||
else
|
||||
Player.Functions.RemoveMoney('bank', price, "Købte møbler")
|
||||
end
|
||||
|
||||
local numFurnitures = #property.propertyData.furnitures
|
||||
|
||||
for i = 1, #items do
|
||||
numFurnitures = numFurnitures + 1
|
||||
property.propertyData.furnitures[numFurnitures] = items[i]
|
||||
end
|
||||
|
||||
property:UpdateFurnitures(property.propertyData.furnitures)
|
||||
|
||||
Framework[Config.Notify].Notify(src, "Du købte møbler for " .. price..",-", "success")
|
||||
|
||||
Framework[Config.Logs].SendLog("**Player ".. GetPlayerName(src) .. "** bought furniture for **$" .. price .. "**")
|
||||
|
||||
Debug("Player bought furniture for $" .. price, "by: " .. GetPlayerName(src))
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:server:removeFurniture", function(property_id, itemid)
|
||||
local src = source
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
if not property:CheckForAccess(citizenid) then return end
|
||||
|
||||
local currentFurnitures = property.propertyData.furnitures
|
||||
|
||||
for k, v in pairs(currentFurnitures) do
|
||||
if v.id == itemid then
|
||||
table.remove(currentFurnitures, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
property:UpdateFurnitures(currentFurnitures)
|
||||
end)
|
||||
|
||||
-- @@ VERY BAD
|
||||
-- I think its not bad anymore but if u got a better idea lmk
|
||||
RegisterNetEvent("ps-housing:server:updateFurniture", function(property_id, item)
|
||||
local src = source
|
||||
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
if not property:CheckForAccess(citizenid) then return end
|
||||
|
||||
local currentFurnitures = property.propertyData.furnitures
|
||||
|
||||
for k, v in pairs(currentFurnitures) do
|
||||
if v.id == item.id then
|
||||
currentFurnitures[k] = item
|
||||
Debug("Updated furniture", json.encode(item))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
property:UpdateFurnitures(currentFurnitures)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:server:addAccess", function(property_id, srcToAdd)
|
||||
local src = source
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
if not property.propertyData.owner == citizenid then
|
||||
-- hacker ban or something
|
||||
Framework[Config.Notify].Notify(src, "Du ejer ikke ejendommen!", "error")
|
||||
return
|
||||
end
|
||||
|
||||
local has_access = property.propertyData.has_access
|
||||
|
||||
local targetCitizenid = GetCitizenid(srcToAdd)
|
||||
local targetPlayer = GetPlayerData(srcToAdd)
|
||||
|
||||
if not property:CheckForAccess(targetCitizenid) then
|
||||
has_access[#has_access+1] = targetCitizenid
|
||||
property:UpdateHas_access(has_access)
|
||||
|
||||
Framework[Config.Notify].Notify(src, "Du gav adgang til " .. targetPlayer.charinfo.firstname .. " " .. targetPlayer.charinfo.lastname, "success")
|
||||
Framework[Config.Notify].Notify(srcToAdd, "Du fik adgang til ejendommen!", "success")
|
||||
else
|
||||
Framework[Config.Notify].Notify(src, "Personen har allerede adgang til ejendommen!", "error")
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-housing:server:removeAccess", function(property_id, citizenidToRemove)
|
||||
local src = source
|
||||
|
||||
local citizenid = GetCitizenid(src)
|
||||
local property = Property.Get(property_id)
|
||||
if not property then return end
|
||||
|
||||
if not property.propertyData.owner == citizenid then
|
||||
-- hacker ban or something
|
||||
Framework[Config.Notify].Notify(src, "Du er ikke ejer af ejendommen!", "error")
|
||||
return
|
||||
end
|
||||
|
||||
local has_access = property.propertyData.has_access
|
||||
local citizenidToRemove = citizenidToRemove
|
||||
|
||||
if property:CheckForAccess(citizenidToRemove) then
|
||||
for i = 1, #has_access do
|
||||
if has_access[i] == citizenidToRemove then
|
||||
table.remove(has_access, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
property:UpdateHas_access(has_access)
|
||||
|
||||
local playerToAdd = QBCore.Functions.GetPlayerByCitizenId(citizenidToRemove) or QBCore.Functions.GetOfflinePlayerByCitizenId(citizenidToRemove)
|
||||
local removePlayerData = playerToAdd.PlayerData
|
||||
local srcToRemove = removePlayerData.source
|
||||
|
||||
Framework[Config.Notify].Notify(src, "Du fjernede adgangen for " .. removePlayerData.charinfo.firstname .. " " .. removePlayerData.charinfo.lastname, "success")
|
||||
|
||||
if srcToRemove then
|
||||
Framework[Config.Notify].Notify(srcToRemove, "Du mistede adgangen til " .. (property.propertyData.street or property.propertyData.apartment) .. " " .. property.property_id, "error")
|
||||
end
|
||||
else
|
||||
Framework[Config.Notify].Notify(src, "Denne person har ikke adgang til ejendommen!", "error")
|
||||
end
|
||||
end)
|
||||
|
||||
lib.callback.register("ps-housing:cb:getPlayersWithAccess", function (source, property_id)
|
||||
local src = source
|
||||
local citizenidSrc = GetCitizenid(src)
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then return end
|
||||
if property.propertyData.owner ~= citizenidSrc then return end
|
||||
|
||||
local withAccess = {}
|
||||
local has_access = property.propertyData.has_access
|
||||
|
||||
for i = 1, #has_access do
|
||||
local citizenid = has_access[i]
|
||||
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid) or QBCore.Functions.GetOfflinePlayerByCitizenId(citizenid)
|
||||
if Player then
|
||||
withAccess[#withAccess + 1] = {
|
||||
citizenid = citizenid,
|
||||
name = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return withAccess
|
||||
end)
|
||||
|
||||
lib.callback.register('ps-housing:cb:getPropertyInfo', function (source, property_id)
|
||||
local src = source
|
||||
local property = Property.Get(property_id)
|
||||
|
||||
if not property then return end
|
||||
|
||||
|
||||
local PlayerData = GetPlayerData(src)
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local onDuty = job.onduty
|
||||
|
||||
if not jobName == Config.RealtorJobName and not onDuty then return end
|
||||
|
||||
local data = {}
|
||||
|
||||
local ownerPlayer, ownerName
|
||||
|
||||
local ownerCid = property.propertyData.owner
|
||||
if ownerCid then
|
||||
ownerPlayer = QBCore.Functions.GetPlayerByCitizenId(ownerCid) or QBCore.Functions.GetOfflinePlayerByCitizenId(ownerCid)
|
||||
ownerName = ownerPlayer.PlayerData.charinfo.firstname .. " " .. ownerPlayer.PlayerData.charinfo.lastname
|
||||
else
|
||||
ownerName = "Ingen ejer"
|
||||
end
|
||||
|
||||
data.owner = ownerName
|
||||
data.street = property.propertyData.street
|
||||
data.region = property.propertyData.region
|
||||
data.description = property.propertyData.description
|
||||
data.for_sale = property.propertyData.for_sale
|
||||
data.price = property.propertyData.price
|
||||
data.shell = property.propertyData.shell
|
||||
data.property_id = property.property_id
|
||||
|
||||
return data
|
||||
end)
|
||||
|
||||
RegisterNetEvent('ps-housing:server:resetMetaData', function()
|
||||
local src = source
|
||||
local Player = QBCore.Functions.GetPlayer(src)
|
||||
local insideMeta = Player.PlayerData.metadata["inside"]
|
||||
|
||||
insideMeta.property_id = nil
|
||||
Player.Functions.SetMetaData("inside", insideMeta)
|
||||
end)
|
1553
resources/[ps]/ps-housing/shared/config.lua
Normal file
1553
resources/[ps]/ps-housing/shared/config.lua
Normal file
File diff suppressed because it is too large
Load Diff
481
resources/[ps]/ps-housing/shared/framework.lua
Normal file
481
resources/[ps]/ps-housing/shared/framework.lua
Normal file
@ -0,0 +1,481 @@
|
||||
Framework = {}
|
||||
|
||||
PoliceJobs = {}
|
||||
|
||||
-- Convert config table to usable keys
|
||||
for i = 1, #Config.PoliceJobNames do
|
||||
PoliceJobs[Config.PoliceJobNames[i]] = true
|
||||
end
|
||||
|
||||
if IsDuplicityVersion() then
|
||||
Framework.ox = {}
|
||||
Framework.qb = {}
|
||||
|
||||
function Framework.ox.Notify(src, message, type)
|
||||
type = type == "inform" and "info" or type
|
||||
TriggerClientEvent("ox_lib:notify", src, {title="Property", description=message, type=type})
|
||||
end
|
||||
|
||||
function Framework.qb.Notify(src, message, type)
|
||||
type = type == "info" and "primary" or type
|
||||
TriggerClientEvent('QBCore:Notify', src, message, type)
|
||||
end
|
||||
|
||||
function Framework.ox.RegisterInventory(stash, label, stashConfig)
|
||||
exports.ox_inventory:RegisterStash(stash, label, stashConfig.slots, stashConfig.maxweight, nil)
|
||||
end
|
||||
|
||||
function Framework.qb.RegisterInventory(stash, label, stashConfig)
|
||||
-- Used for ox_inventory compat
|
||||
end
|
||||
|
||||
function Framework.qb.SendLog(message)
|
||||
if Config.EnableLogs then
|
||||
TriggerEvent('qb-log:server:CreateLog', 'pshousing', 'Housing System', 'blue', message)
|
||||
end
|
||||
end
|
||||
|
||||
function Framework.ox.SendLog(message)
|
||||
-- noop
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local function hasApartment(apts)
|
||||
for propertyId, _ in pairs(apts) do
|
||||
local property = PropertiesTable[propertyId]
|
||||
if property.owner then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
Framework.qb = {
|
||||
|
||||
Notify = function(message, type)
|
||||
type = type == "info" and "primary" or type
|
||||
TriggerEvent('QBCore:Notify', message, type)
|
||||
end,
|
||||
|
||||
AddEntrance = function(coords, size, heading, propertyId, enter, raid, showcase, showData, targetName)
|
||||
local property_id = propertyId
|
||||
exports["qb-target"]:AddBoxZone(
|
||||
targetName,
|
||||
vector3(coords.x, coords.y, coords.z),
|
||||
size.x,
|
||||
size.y,
|
||||
{
|
||||
name = targetName,
|
||||
heading = heading,
|
||||
debugPoly = Config.DebugMode,
|
||||
minZ = coords.z - 1.5,
|
||||
maxZ = coords.z + 2.0,
|
||||
},
|
||||
{
|
||||
options = {
|
||||
{
|
||||
label = "Gå indenfor",
|
||||
icon = "fas fa-door-open",
|
||||
action = enter,
|
||||
canInteract = function()
|
||||
local property = Property.Get(property_id)
|
||||
return property.has_access or property.owner
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Fremvis ejendom",
|
||||
icon = "fas fa-eye",
|
||||
action = showcase,
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local onDuty = job.onduty
|
||||
return jobName == Config.RealtorJobName and onDuty
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ejendoms info",
|
||||
icon = "fas fa-circle-info",
|
||||
action = showData,
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local onDuty = job.onduty
|
||||
return jobName == Config.RealtorJobName and onDuty
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ring Doorbell",
|
||||
icon = "fas fa-bell",
|
||||
action = enter,
|
||||
canInteract = function()
|
||||
local property = Property.Get(property_id)
|
||||
return not property.has_access and not property.owner
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ring på døren",
|
||||
icon = "fas fa-building-shield",
|
||||
action = raid,
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local gradeAllowed = tonumber(job.grade.level) >= Config.MinGradeToRaid
|
||||
local onDuty = job.onduty
|
||||
|
||||
return PoliceJobs[jobName] and gradeAllowed and onDuty
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return targetName
|
||||
end,
|
||||
|
||||
AddApartmentEntrance = function(coords, size, heading, apartment, enter, seeAll, seeAllToRaid, targetName)
|
||||
exports['qb-target']:AddBoxZone(targetName, vector3(coords.x, coords.y, coords.z), size.x, size.y, {
|
||||
name = targetName,
|
||||
heading = heading,
|
||||
debugPoly = Config.DebugMode,
|
||||
minZ = coords.z - 1.0,
|
||||
maxZ = coords.z + 2.0,
|
||||
}, {
|
||||
options = {
|
||||
{
|
||||
label = "Gå indenfor",
|
||||
action = enter,
|
||||
icon = "fas fa-door-open",
|
||||
canInteract = function()
|
||||
local apartments = ApartmentsTable[apartment].apartments
|
||||
return hasApartment(apartments)
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Se alle lejligheder",
|
||||
icon = "fas fa-circle-info",
|
||||
action = seeAll,
|
||||
},
|
||||
{
|
||||
label = "Ransag lejlighed",
|
||||
action = seeAllToRaid,
|
||||
icon = "fas fa-building-shield",
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local gradeAllowed = tonumber(job.grade.level) >= Config.MinGradeToRaid
|
||||
local onDuty = job.onduty
|
||||
|
||||
return PoliceJobs[jobName] and gradeAllowed and onDuty
|
||||
end,
|
||||
},
|
||||
}
|
||||
})
|
||||
end,
|
||||
|
||||
AddDoorZoneInside = function(coords, size, heading, leave, checkDoor)
|
||||
exports["qb-target"]:AddBoxZone(
|
||||
"shellExit",
|
||||
vector3(coords.x, coords.y, coords.z),
|
||||
size.x,
|
||||
size.y,
|
||||
{
|
||||
name = "shellExit",
|
||||
heading = heading,
|
||||
debugPoly = Config.DebugMode,
|
||||
minZ = coords.z - 2.0,
|
||||
maxZ = coords.z + 1.0,
|
||||
},
|
||||
{
|
||||
options = {
|
||||
{
|
||||
label = "Forlad ejendom",
|
||||
action = leave,
|
||||
icon = "fas fa-right-from-bracket",
|
||||
},
|
||||
{
|
||||
label = "Tjek døren",
|
||||
action = checkDoor,
|
||||
icon = "fas fa-bell",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return "shellExit"
|
||||
end,
|
||||
|
||||
AddDoorZoneInsideTempShell = function(coords, size, heading, leave)
|
||||
exports["qb-target"]:AddBoxZone(
|
||||
"shellExit",
|
||||
vector3(coords.x, coords.y, coords.z),
|
||||
size.x,
|
||||
size.y,
|
||||
{
|
||||
name = "shellExit",
|
||||
heading = heading,
|
||||
debugPoly = Config.DebugMode,
|
||||
minZ = coords.z - 2.0,
|
||||
maxZ = coords.z + 1.0,
|
||||
},
|
||||
{
|
||||
options = {
|
||||
{
|
||||
label = "Forlad",
|
||||
action = leave,
|
||||
icon = "fas fa-right-from-bracket",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return "shellExit"
|
||||
end,
|
||||
|
||||
RemoveTargetZone = function(targetName)
|
||||
exports["qb-target"]:RemoveZone(targetName)
|
||||
end,
|
||||
|
||||
AddRadialOption = function(id, label, icon, _, event, options)
|
||||
exports['qb-radialmenu']:AddOption({
|
||||
id = id,
|
||||
title = label,
|
||||
icon = icon,
|
||||
type = 'client',
|
||||
event = event,
|
||||
shouldClose = true,
|
||||
options = options
|
||||
}, id)
|
||||
end,
|
||||
|
||||
RemoveRadialOption = function(id)
|
||||
exports['qb-radialmenu']:RemoveOption(id)
|
||||
end,
|
||||
|
||||
AddTargetEntity = function (entity, label, icon, action)
|
||||
exports["qb-target"]:AddTargetEntity(entity, {
|
||||
options = {
|
||||
{
|
||||
label = label,
|
||||
icon = icon,
|
||||
action = action,
|
||||
},
|
||||
},
|
||||
})
|
||||
end,
|
||||
|
||||
RemoveTargetEntity = function (entity)
|
||||
exports["qb-target"]:RemoveTargetEntity(entity)
|
||||
end,
|
||||
|
||||
OpenInventory = function (stash, stashConfig)
|
||||
TriggerServerEvent("inventory:server:OpenInventory", "stash", stash, stashConfig)
|
||||
TriggerEvent("inventory:client:SetCurrentStash", stash)
|
||||
end,
|
||||
}
|
||||
|
||||
Framework.ox = {
|
||||
Notify = function(message, type)
|
||||
type = type == "inform" and "info" or type
|
||||
|
||||
lib.notify({
|
||||
title = 'Ejendom',
|
||||
description = message,
|
||||
type = type
|
||||
})
|
||||
end,
|
||||
|
||||
AddEntrance = function (coords, size, heading, propertyId, enter, raid, showcase, showData, _)
|
||||
local property_id = propertyId
|
||||
|
||||
local handler = exports.ox_target:addBoxZone({
|
||||
coords = vector3(coords.x, coords.y, coords.z),
|
||||
size = vector3(size.y, size.x, size.z),
|
||||
rotation = heading,
|
||||
debug = Config.DebugMode,
|
||||
options = {
|
||||
{
|
||||
label = "Gå indenfor",
|
||||
icon = "fas fa-door-open",
|
||||
onSelect = enter,
|
||||
canInteract = function()
|
||||
local property = Property.Get(property_id)
|
||||
return property.has_access or property.owner
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Fremvis ejendom",
|
||||
icon = "fas fa-eye",
|
||||
onSelect = showcase,
|
||||
canInteract = function()
|
||||
-- local property = Property.Get(property_id)
|
||||
-- if property.propertyData.owner ~= nil then return false end -- if its owned, it cannot be showcased
|
||||
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
|
||||
return jobName == Config.RealtorJobName
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ejendoms info",
|
||||
icon = "fas fa-circle-info",
|
||||
onSelect = showData,
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local onDuty = job.onduty
|
||||
return jobName == Config.RealtorJobName and onDuty
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ring på døren",
|
||||
icon = "fas fa-bell",
|
||||
onSelect = enter,
|
||||
canInteract = function()
|
||||
local property = Property.Get(property_id)
|
||||
return not property.has_access and not property.owner
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Ransag lejlighed",
|
||||
icon = "fas fa-building-shield",
|
||||
onSelect = raid,
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local gradeAllowed = tonumber(job.grade.level) >= Config.MinGradeToRaid
|
||||
local onDuty = job.onduty
|
||||
|
||||
return PoliceJobs[jobName] and onDuty and gradeAllowed
|
||||
end,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return handler
|
||||
end,
|
||||
|
||||
AddApartmentEntrance = function (coords, size, heading, apartment, enter, seeAll, seeAllToRaid, _)
|
||||
local handler = exports.ox_target:addBoxZone({
|
||||
coords = vector3(coords.x, coords.y, coords.z),
|
||||
size = vector3(size.y, size.x, size.z),
|
||||
rotation = heading,
|
||||
debug = Config.DebugMode,
|
||||
options = {
|
||||
{
|
||||
label = "Gå indenfor",
|
||||
onSelect = enter,
|
||||
icon = "fas fa-door-open",
|
||||
canInteract = function()
|
||||
local apartments = ApartmentsTable[apartment].apartments
|
||||
return hasApartment(apartments)
|
||||
end,
|
||||
},
|
||||
{
|
||||
label = "Se alle lejligheder",
|
||||
onSelect = seeAll,
|
||||
icon = "fas fa-circle-info",
|
||||
},
|
||||
{
|
||||
label = "Ransag lejlighed",
|
||||
onSelect = seeAllToRaid,
|
||||
icon = "fas fa-building-shield",
|
||||
canInteract = function()
|
||||
local job = PlayerData.job
|
||||
local jobName = job.name
|
||||
local gradeAllowed = tonumber(job.grade.level) >= Config.MinGradeToRaid
|
||||
local onDuty = job.onduty
|
||||
|
||||
return PoliceJobs[jobName] and onDuty and gradeAllowed
|
||||
end,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return handler
|
||||
end,
|
||||
|
||||
AddDoorZoneInside = function (coords, size, heading, leave, checkDoor)
|
||||
local handler = exports.ox_target:addBoxZone({
|
||||
coords = vector3(coords.x, coords.y, coords.z), --z = 3.0
|
||||
size = vector3(size.y, size.x, size.z),
|
||||
rotation = heading,
|
||||
debug = Config.DebugMode,
|
||||
options = {
|
||||
{
|
||||
name = "leave",
|
||||
label = "Forlad ejendom",
|
||||
onSelect = leave,
|
||||
icon = "fas fa-right-from-bracket",
|
||||
},
|
||||
{
|
||||
name = "doorbell",
|
||||
label = "Tjek døren",
|
||||
onSelect = checkDoor,
|
||||
icon = "fas fa-bell",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return handler
|
||||
end,
|
||||
|
||||
AddDoorZoneInsideTempShell = function (coords, size, heading, leave)
|
||||
local handler = exports.ox_target:addBoxZone({
|
||||
coords = vector3(coords.x, coords.y, coords.z), --z = 3.0
|
||||
size = vector3(size.y, size.x, size.z),
|
||||
rotation = heading,
|
||||
debug = Config.DebugMode,
|
||||
options = {
|
||||
{
|
||||
name = "leave",
|
||||
label = "Forlad",
|
||||
onSelect = leave,
|
||||
icon = "fas fa-right-from-bracket",
|
||||
},
|
||||
},
|
||||
})
|
||||
print("made")
|
||||
return handler
|
||||
end,
|
||||
|
||||
RemoveTargetZone = function (handler)
|
||||
exports.ox_target:removeZone(handler)
|
||||
end,
|
||||
|
||||
AddRadialOption = function(id, label, icon, fn)
|
||||
lib.addRadialItem({
|
||||
id = id,
|
||||
icon = icon,
|
||||
label = label,
|
||||
onSelect = fn,
|
||||
})
|
||||
end,
|
||||
|
||||
RemoveRadialOption = function(id)
|
||||
lib.removeRadialItem(id)
|
||||
end,
|
||||
|
||||
AddTargetEntity = function (entity, label, icon, action)
|
||||
exports.ox_target:addLocalEntity(entity, {
|
||||
{
|
||||
name = label,
|
||||
label = label,
|
||||
icon = icon,
|
||||
onSelect = action,
|
||||
},
|
||||
})
|
||||
end,
|
||||
|
||||
RemoveTargetEntity = function (entity)
|
||||
exports.ox_target:removeLocalEntity(entity)
|
||||
end,
|
||||
|
||||
OpenInventory = function (stash, stashConfig)
|
||||
exports.ox_inventory:openInventory('stash', stash)
|
||||
end,
|
||||
}
|
BIN
resources/[ps]/ps-housing/stream/_manifest.ymf
Normal file
BIN
resources/[ps]/ps-housing/stream/_manifest.ymf
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/container_shell.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/container_shell.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/frankaunttextures.ytd
Normal file
BIN
resources/[ps]/ps-housing/stream/frankaunttextures.ytd
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/furnitured_midapart.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/furnitured_midapart.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/lesters_txd.ytd
Normal file
BIN
resources/[ps]/ps-housing/stream/lesters_txd.ytd
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/modernhotel_shell.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/modernhotel_shell.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_aqua.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_aqua.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_aqua.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_aqua.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_black.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_black.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_black.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_black.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_green.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_green.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_green.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_green.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_grey.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_grey.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_grey.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_grey.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_purple.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_purple.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_purple.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_purple.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_red.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_red.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_red.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_red.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_wall.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_wall.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_wall.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_wall.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_white.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_white.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_white.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_white.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_yellow.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_yellow.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/ps_wall_yellow.ytyp
Normal file
BIN
resources/[ps]/ps-housing/stream/ps_wall_yellow.ytyp
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_frankaunt.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_frankaunt.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_garagem.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_garagem.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_lester.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_lester.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_michael.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_michael.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_office1.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_office1.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_office1_txd.ytd
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_office1_txd.ytd
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_ranch.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_ranch.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_store1.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_store1.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_trailer.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_trailer.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_trevor.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_trevor.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_v16low.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_v16low.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_v16mid.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_v16mid.ydr
Normal file
Binary file not shown.
BIN
resources/[ps]/ps-housing/stream/shell_warehouse1.ydr
Normal file
BIN
resources/[ps]/ps-housing/stream/shell_warehouse1.ydr
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user