1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-07-20 19:03:04 +02:00
parent 4f7a53e4a7
commit 10de5a6c76
33 changed files with 2124 additions and 0 deletions

View file

@ -0,0 +1,17 @@
# 💬 Dialog
This is a Dialog script where you can talk to specific peds you spawn in config or in other scripts. <br>
## ❓Installation
Pretty simple install given the ui is already built for you. Just drag the root folder into your servers resources folder and make sure to ensure the folder inside of your server.cfg.
## ❗ Dependencies
This resouce is very light on depenedacies. If you do not plan to use this script to spawn peds and would rather spawn them via you own script there are 0 dependencies. Otherwise you will need [ox_target](https://github.com/overextended/ox_target) or [qb-target](https://github.com/qbcore-framework/qb-target)
## Other Info
[**Discord**](https://discord.gg/peYKn8CxHG) <br>
[**Preview**](https://youtu.be/0JWGxLMnOic) <br>
[**Forums**](https://forum.cfx.re/t/free-npc-dialog/5200606) <br>
[**Documentation**](https://st4lth.gitbook.io/st4lth/dialog) <br>
![Picture](https://i.imgur.com/4llTxBH.jpeg)

View file

@ -0,0 +1,125 @@
Open = false
cam = nil
Peds = {}
local Actions = {}
if Config.FrameworkLoadinEvent ~= '' then
RegisterNetEvent(Config.FrameworkLoadinEvent, function()
SpawnPeds()
end)
end
AddEventHandler('onResourceStart', function(resourceName)
if (GetCurrentResourceName() == resourceName) then
if #Config.peds ~= 0 then SpawnPeds() end
end
end)
---@param ped number # The id of the ped
---@param data table # The data for the interaction
---@param zoom number # Camera zoom level (optional, default: 40.0)
---@param x number # Camera X position (optional, default: 0)
---@param y number # Camera Y position (optional, default: 1.5)
---@param z number # Camera Z position (optional, default: 0.3)
---@param rotX number # Camera X rotation (optional, default: 0.0)
---@param rotY number # Camera Y rotation (optional, default: 0.0)
---@param rotZ number # Camera Z rotation (optional, default: GetEntityHeading(ped) + 180)
OpenDialog = function(ped, data, zoom, x, y, z, rotX, rotY, rotZ)
-- Setting defaults
local newX, newY, newZ = x or 0, y or 1.5, z or 0.3
local newRotX, newRotY, newRotZ = rotX or 0.0, rotY or 0.0, rotZ or GetEntityHeading(ped) + 180
local fov = zoom or 40.0
local coords = GetOffsetFromEntityInWorldCoords(ped, newX, newY, newZ)
-- camera setup
cam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetEntityLocallyInvisible(PlayerPedId())
SetCamActive(cam, true)
RenderScriptCams(true, true, 500, true, true)
SetCamCoord(cam, coords.x, coords.y, coords.z + 0.2)
SetCamRot(cam, newRotX, newRotY, newRotZ, 5)
SetCamFov(cam, fov)
local Dialog = data
local function extractEvents(tbl)
for k, v in pairs(tbl) do
if type(v) == "table" then
extractEvents(v)
elseif k == "event" and type(v) == "string" then
Actions[v] = v
end
end
end
extractEvents(Dialog)
SetNuiFocus(true, true)
SendNUIMessage({
type = 'New',
data = data
})
Open = true
SetInvisible()
end
SetDialog = function(data)
SendNUIMessage({
type = 'Set',
data = data
})
end
CloseDialog = function()
Open = false
SendNUIMessage({
type = 'Close',
})
end
SpawnPedByID = function(id, data)
Peds[id] = data
SpawnPed(id, data)
end
DeletePedByID = function(id)
if Peds[id].ped then
exports['qb-target']:RemoveTargetEntity(Peds[id].ped)
DeleteEntity(Peds[id].ped)
Peds[id] = nil
end
end
RegisterNuiCallback("click", function(data, cb)
if data.data then
SendNUIMessage({
type = 'Continue',
data = data.data
})
cb(false)
return
end
if data.close then
SetNuiFocus(false, false)
if cam and DoesCamExist(cam) then
RenderScriptCams(false, true, 500, true, true)
DestroyCam(cam, true)
end
CloseDialog()
end
if Actions[data.event] then
if data.server then
TriggerServerEvent(Actions[data.event], data)
else
TriggerEvent(Actions[data.event], data)
end
end
end)
exports('OpenDialog', OpenDialog)
exports('SetDialog', SetDialog)
exports('CloseDialog', CloseDialog)
exports('SpawnPed', SpawnPedByID)

View file

@ -0,0 +1,87 @@
RegisterCommand('deleteped', function()
DeletePedByID('test')
end, false)
RegisterCommand('spawnped', function()
SpawnPedByID('test', {
label = 'Talk to stranger',
icon = 'fa-solid fa-comment',
model = "csb_avon",
coords = vector3(165.48, 6612.81, 31.9),
heading = 170,
data = {
firstname = 'John',
lastname = 'Doe',
text = 'Hey bud, how ya doin.',
buttons = {
{
text = 'Im ok, how are you?',
data = {
text = 'Im cool rn, see you around!',
buttons = {
{
text = 'Se ya',
close = true
},
}
}
},
{
text = 'No sorry, im gonna leave',
close = true
},
}
}
})
end, false)
RegisterNetEvent('con:mechanic', function(ped)
rep = 1
data = {
firstname = 'John',
lastname = 'Doe',
text = 'Hey bud, what can i do for you',
type = 'Mechanic',
rep = rep,
buttons = {
{ text = "I wanna clock in", data = {
text = 'Alright',
buttons = {
{ text = 'Clock in/out', event = 'con:clockin', close = true },
{ text = 'Whatever, changed my mind', event = 'con:back' },
}
}},
{ text = "I'm gonna leave", close = true },
}
}
OpenDialog(ped, data)
end)
RegisterNetEvent('con:back', function()
data = {
firstname = 'John',
lastname = 'Doe',
text = 'Hey bud, what can i do for you',
type = 'Mechanic',
rep = '2',
buttons = {
{ text = "I wanna clock in", data = {
text = 'Alright',
buttons = {
{ text = 'Clock in/out', event = 'con:clockin', close = true },
{ text = 'Whatever changed my mind', event = 'con:back' },
}
}
},
{ text = "I'm gonna leave", close = true },
}
}
SetDialog(data)
end)
RegisterNetEvent('con:clockin', function()
print('123')
TriggerEvent('QBCore:Notify', "clocked in", 'success')
end)

View file

@ -0,0 +1,148 @@
local loadModel = function(modelHash)
if not IsModelValid(modelHash) then
print(modelHash..' Is not a valid model')
return
end
RequestModel(modelHash)
RequestCollisionForModel(modelHash)
while not HasModelLoaded(modelHash) or not HasCollisionForModelLoaded(modelHash) do
Wait(10)
end
end
local getClosestPed = function(coords, max)
local all = GetGamePool('CPed')
local closest, closestCoords
max = max or 2.0
for i = 1, #all do
local ped = all[i]
if IsPedAPlayer(ped) then
return
end
local pedCoords = GetEntityCoords(ped)
local distance = #(coords - pedCoords)
if distance < max then
max = distance
closest = ped
closestCoords = pedCoords
end
end
return closest, closestCoords
end
local loadanimDict = function(animDict)
if not DoesAnimDictExist(animDict) then
print(animDict..' Is not a valid animation dict')
return
end
RequestAnimDict(animDict)
while not HasAnimDictLoaded(animDict) do
Wait(10)
end
end
local playAnimation = function(animData)
if not IsEntityPlayingAnim(animData.ped, animData.dict, animData.lib, 3) then
if not animData.flag then animData.flag = 49 end
loadanimDict(animData.dict)
TaskPlayAnim(animData.ped, animData.dict, animData.lib, 2.0, -1.0, -1, animData.flag, 0, 0, 0, 0)
end
end
SpawnPeds = function()
for v, k in pairs(Config.peds) do
Peds[v] = k
SpawnPed(v, k)
end
end
SpawnPed = function(id, data)
local ped, pedDist = getClosestPed(data.coords)
if data.dict then
dict = data.dict
else
dict = "missbigscore2aig_6"
end
if data.lib then
lib = data.lib
else
lib = "wait_loop"
end
if DoesEntityExist(ped) and pedDist <= 1.2 then
DeletePed(ped)
end
loadModel(data.model)
Peds[id].ped = CreatePed(5, GetHashKey(data.model), data.coords.x, data.coords.y, data.coords.z - 1.0, data.heading, false, false)
SetEntityHeading(Peds[id].ped, data.heading)
SetPedCombatAttributes(Peds[id].ped, 46, true)
SetPedFleeAttributes(Peds[id].ped, 0, 0)
SetBlockingOfNonTemporaryEvents(Peds[id].ped, true)
SetEntityAsMissionEntity(Peds[id].ped, true, true)
FreezeEntityPosition(Peds[id].ped, true)
SetEntityInvincible(Peds[id].ped, true)
SetPedDiesWhenInjured(Peds[id].ped, false)
SetPedHearingRange(Peds[id].ped, 1.0)
SetPedAlertness(Peds[id].ped, 0)
if data.type ~= 'scenario' then
playAnimation({
ped = Peds[id].ped,
dict = dict,
lib = lib,
flag = 1
})
else
TaskStartScenarioInPlace(Peds[id].ped, data.anim, 0, false)
end
opts = {
label = data.label,
icon = data.icon,
action = function()
if data.data then
OpenDialog(Peds[id].ped, data.data)
else
if data.server then
TriggerServerEvent(data.event, Peds[id].ped)
else
TriggerEvent(data.event, Peds[id].ped)
end
end
end
}
if Config.Target == 'qb' then
exports['qb-target']:AddTargetEntity(Peds[id].ped, {
options = { opts },
distance = 2.0
})
elseif Config.Target == 'ox' then
exports.ox_target:addLocalEntity(Peds[id].ped, {opts}) -- Use opts directly
else
print("^1[ERROR] Invalid Target Config! Check your Config.Target setting.^0")
end
end
SetInvisible = function()
while Open do
SetEntityLocallyInvisible(PlayerPedId())
Wait(5)
end
end

View file

@ -0,0 +1,60 @@
Config = {
Target = 'qb',
FrameworkLoadinEvent = 'QBCore:Client:OnPlayerLoaded',
peds = {
['hafenarbeiter'] = {
label = 'Red mit dem Hafenarbeiter',
icon = 'fa-solid fa-hard-hat',
model = "s_m_m_dockwork_01",
coords = vector3(1234.56, -3210.45, 5.9),
heading = 90,
data = {
firstname = 'Kalle',
lastname = 'Kutter',
text = "Moin... du wirkst nich wie jemand, der hier offiziell was abholen will.",
buttons = {
{
text = "Kommt drauf an, was es hier so gibt...",
data = {
text = "Hehehe... naja, sagen wir mal so: Manche Container stehen nachts ein bisschen... unbeaufsichtigt rum.",
buttons = {
{
text = "Ach ja? Und dann?",
data = {
text = "Na, wenn einer wüsste, wie man da *rein* kommt... bräuchte er sicher was mit Kraft oder Strom, verstehste?",
buttons = {
{
text = "Klar. Ich versteh schon.",
data = {
text = "Gut. Dann hab ich dir ja nix gesagt, oder?",
buttons = {
{
text = "Du hast mich nie gesehen.",
close = true
}
}
}
},
{
text = "Klingt mir zu heiß...",
close = true
}
}
}
},
{
text = "Ich glaub, ich hab mich verlaufen...",
close = true
}
}
}
},
{
text = "Nur mal umschauen, Chef.",
close = true
}
}
}
}
} -- schließt `peds`
} -- schließt `Config`

View file

@ -0,0 +1,24 @@
fx_version 'cerulean'
game 'gta5'
author 'ST4lTH'
description 'Dialog'
version '1.0.0'
client_scripts {
'client/*.lua',
}
shared_scripts {
'config.lua',
}
files {
'web/dist/index.html',
'web/dist/**/*',
}
lua54 'yes'
ui_page 'web/dist/index.html'
--ui_page 'http://localhost:3000/' -- Dev

View file

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

View file

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

22
resources/[tools]/dialog/web/.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
.DS_Store
node_modules
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,44 @@
# default
## Project setup
```
# yarn
yarn
# npm
npm install
# pnpm
pnpm install
```
### Compiles and hot-reloads for development
```
# yarn
yarn dev
# npm
npm run dev
# pnpm
pnpm dev
```
### Compiles and minifies for production
```
# yarn
yarn build
# npm
npm run build
# pnpm
pnpm build
```
### Customize configuration
See [Configuration Reference](https://vitejs.dev/config/).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Vuetify 3</title>
<script type="module" crossorigin src="./assets/index-8c3f21c3.js"></script>
<link rel="stylesheet" href="./assets/index-a3506647.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Vuetify 3</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
{
"name": "web",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@mdi/font": "7.0.96",
"roboto-fontface": "*",
"vue": "^3.2.0",
"vuetify": "^3.0.0",
"webfontloader": "^1.0.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"sass": "^1.69.7",
"vite": "^4.2.0",
"vite-plugin-vuetify": "^1.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,165 @@
<template>
<v-app style="background: transparent!important;">
<v-fade-transition>
<div class="dialog-bg" v-if="show">
<v-card color="bg" class="card ">
<v-card-title class="title">
<div>
<span class="font-weight-bold mr-2">
{{ data.firstname }}
</span>
<span class="font-weight-light">
{{ data.lastname }}
</span>
</div>
<div style="flex-grow: 1;">
</div>
<div class="note" v-if="data.rep">
{{ data.rep }} Rep
</div>
<div class="note" v-if="data.type">
{{ data.type }}
</div>
</v-card-title>
<div class="text-dialog">
<div class="text bg-grey-darken-4 rounded">
{{ data.text }}
</div>
<div class="buttons">
<div class="button" v-for="(item, index) in data.buttons" @keyup.index="click(item)" @click="click(item)">
<div class="number">
<p>
{{ index+1 }}
</p>
</div>
{{ item.text }}
</div>
</div>
</div>
</v-card>
</div>
</v-fade-transition>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
},
data: () => ({
show: false,
data: {
firstname: 'John',
lastname: 'Doe',
text: 'Lorem ipsum dolor sit amet',
type: '',
rep: '',
buttons: [
{ text: 'Lorem ipsum dolor sit amet' }
]
}
}),
methods: {
click(data){
post('click', data, (resp) => {
if (resp == 'close') {
this.show = false;
this.data = null;
}
})
},
},
mounted() {
this.escapeListener = window.addEventListener("keyup", (event) => {
if (!this.show) {
return
}
if (event.keyCode || 49 && event.keyCode || 51 && event.keyCode || 52 && event.keyCode || 53) {
if ( this.data.buttons[event.keyCode - 49] ) {
this.click(this.data.buttons[event.keyCode - 49])
}
}
});
this.messageListener = window.addEventListener("message", (event) => {
const item = event.data || event.detail; //'detail' is for debugging via browsers
if (item.type == 'New') {
this.data = item.data
this.show = true
} else if (item.type == 'Continue') {
this.data.text = item.data.text
this.data.buttons = item.data.buttons
} else if (item.type == 'Set') {
this.data = item.data
this.show = true
} else if (item.type == 'Close') {
this.show = false;
this.data = null;
}
});
},
}
const resource = 'dialog';
const post = (event, data, cb) => {
if (event) {
fetch(`https://${resource}/${event}`, {
method: "POST",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify(data || {}),
})
.then((resp) => resp.json())
.then((resp) => {
if (cb) {
cb(resp);
}
});
}
};
</script>
<style>
html {
overflow: hidden!important;
}
:root {
color-scheme: light !important;
}
#app {
background: transparent!important;
background-color: transparent!important;
}
/* width */
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,6 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/>
<path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/>
<path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/>
<path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 526 B

View file

@ -0,0 +1,68 @@
.dialog-bg {
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(133,133,133,0) 41%, rgba(1,214,193,0.3491771708683473) 100%);
}
.card {
position: absolute!important;
right: 0;
left: 0;
margin: auto !important;
width: fit-content;
bottom: 10vh;
.title {
display: flex;
align-items: center;
.note {
margin-left: 10px;
height: fit-content;
font-size: 15px;
background-color: rgba(0, 255, 255, 0.681);
border-radius: 5px;
color: #0e0e0e;
padding: 8px;
line-height: 6px;
}
}
.text-dialog {
margin: 2px 10px 10px 10px;
.text {
width: 610px;
padding: 5px 10px;
margin-bottom: 10px;
background: radial-gradient(circle, rgb(58, 58, 58) 0%, rgb(36, 36, 36) 100%);
}
.buttons {
display: grid;
grid-template-columns: 302px 302px;
grid-row: auto auto;
grid-column-gap: 7px;
grid-row-gap: 7px;
.button {
display: flex;
padding: 3px;
border-radius: 4px;
font-size: 12px;
align-items: center;
background-color: rgb(36, 36, 36);
.number {
margin-right: 8px;
width: 23px;
height: 23px;
border-radius: 4px;
background-color: rgb(0, 255, 255);
p {
color: #414041;
text-align: center;
font-weight: bold;
font-size: 15px;
}
}
}
}
}
}

View file

@ -0,0 +1,23 @@
/**
* main.js
*
* Bootstraps Vuetify and other plugins then mounts the App`
*/
// Components
import './assets/styles/style.scss'
import App from './App.vue'
// Composables
import { createApp } from 'vue'
// Plugins
import { registerPlugins } from '@/plugins'
const app = createApp(App)
registerPlugins(app)
app.mount('#app')

View file

@ -0,0 +1,14 @@
/**
* plugins/index.js
*
* Automatically included in `./src/main.js`
*/
// Plugins
import { loadFonts } from './webfontloader'
import vuetify from './vuetify'
export function registerPlugins (app) {
loadFonts()
app.use(vuetify)
}

View file

@ -0,0 +1,28 @@
/**
* plugins/vuetify.js
*
* Framework documentation: https://vuetifyjs.com`
*/
// Styles
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
// Composables
import { createVuetify } from 'vuetify'
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'dark',
themes: {
dark: {
colors: {
primary: '#1867C0',
secondary: '#5CBBF6',
bg: '#121212'
},
},
},
},
})

View file

@ -0,0 +1,15 @@
/**
* plugins/webfontloader.js
*
* webfontloader documentation: https://github.com/typekit/webfontloader
*/
export async function loadFonts () {
const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader')
webFontLoader.load({
google: {
families: ['Roboto:100,300,400,500,700,900&display=swap'],
},
})
}

View file

@ -0,0 +1,42 @@
// Plugins
import vue from '@vitejs/plugin-vue'
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
// Utilities
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls }
}),
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
vuetify({
autoImport: true,
}),
],
define: { 'process.env': {} },
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
extensions: [
'.js',
'.json',
'.jsx',
'.mjs',
'.ts',
'.tsx',
'.vue',
],
},
server: {
port: 3000,
},
build: {
outDir: "./dist",
},
base: "",
})