z
This commit is contained in:
parent
7f16dedc09
commit
bf1221578d
397
README.md
397
README.md
|
@ -1,19 +1,9 @@
|
||||||
<div align="center">
|
<p align="center">
|
||||||
<img href="https://projecterror.dev" width="150" src="https://i.tasoagc.dev/c1pD" alt="Material-UI logo" />
|
<a href="" rel="noopener">
|
||||||
</div>
|
<img width=200px height=200px src="https://lorraxs.com/logo.svg" alt="Project logo"></a>
|
||||||
<h1 align="center">FiveM React and Lua Boilerplate</h1>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<h1 align="center">LR Fivem lua module</h1>
|
||||||
A simple and extendable React (TypeScript) boilerplate designed around the Lua ScRT
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/project-error/pe-utils/master/LICENSE)
|
|
||||||
![Discord](https://img.shields.io/discord/791854454760013827?label=Our%20Discord)
|
|
||||||
![David](https://img.shields.io/david/project-error/fivem-react-boilerplate-lua)
|
|
||||||
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=project-error/fivem-react-boilerplate-lua)](https://dependabot.com)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
This repository is a basic boilerplate for getting started
|
This repository is a basic boilerplate for getting started
|
||||||
with React in NUI. It contains several helpful utilities and
|
with React in NUI. It contains several helpful utilities and
|
||||||
|
@ -27,11 +17,13 @@ production build
|
||||||
This version of the boilerplate is meant for the CfxLua runtime.
|
This version of the boilerplate is meant for the CfxLua runtime.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* [Node > v10.6](https://nodejs.org/en/)
|
|
||||||
* [Yarn](https://yarnpkg.com/getting-started/install) (Preferred but not required)
|
|
||||||
|
|
||||||
*A basic understanding of the modern web development workflow. If you don't
|
- [Node > v10.6](https://nodejs.org/en/)
|
||||||
know this yet, React might not be for you just yet.*
|
- [Yarn](https://yarnpkg.com/getting-started/install) (Preferred but not required)
|
||||||
|
- [ox_lib](https://github.com/overextended/ox_lib)
|
||||||
|
|
||||||
|
_A basic understanding of the modern web development workflow. If you don't
|
||||||
|
know this yet, React might not be for you just yet._
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
@ -40,177 +32,244 @@ it within your `resources` folder
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
*The boilerplate was made using `yarn` but is still compatible with
|
_The boilerplate was made using `yarn` but is still compatible with
|
||||||
`npm`.*
|
`npm`._
|
||||||
|
|
||||||
Install dependencies by navigating to the `web` folder within
|
Install dependencies by navigating to the `web` folder within
|
||||||
a terminal of your choice and type `npm i` or `yarn`.
|
a terminal of your choice and type `npm i` or `yarn`.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
This boilerplate comes with some utilities and examples to work off of.
|
# Enable modules in Config.EnableModules
|
||||||
|
|
||||||
### Lua Utils
|
|
||||||
|
|
||||||
**SendReactMessage**
|
|
||||||
|
|
||||||
This is a small wrapper for dispatching NUI messages. This is designed
|
|
||||||
to be used with the `useNuiEvent` React hook.
|
|
||||||
|
|
||||||
Signature
|
|
||||||
```lua
|
```lua
|
||||||
---@param action string The action you wish to target
|
Config.EnableModules = {
|
||||||
---@param data any The data you wish to send along with this action
|
["Newbie"] = false,
|
||||||
SendReactMessage(action, data)
|
["DamageLog"] = true,
|
||||||
```
|
["Hud"] = true,
|
||||||
|
['Player'] = true,
|
||||||
Usage
|
['Speedometer'] = true
|
||||||
```lua
|
|
||||||
SendReactMessage('setVisible', true)
|
|
||||||
```
|
|
||||||
|
|
||||||
**debugPrint**
|
|
||||||
|
|
||||||
A debug printing utility that is dependent on a convar,
|
|
||||||
if the convar is set this will print out to the console.
|
|
||||||
|
|
||||||
The convar is dependent on the name given to the resource.
|
|
||||||
It follows this format `YOUR_RESOURCE_NAME-debugMode`
|
|
||||||
|
|
||||||
To turn on debugMode add `setr YOUR_RESOURCE_NAME-debugMode 1` to
|
|
||||||
your server.cfg or use the `setr` console command instead.
|
|
||||||
|
|
||||||
Signature (Replicates `print`)
|
|
||||||
```lua
|
|
||||||
---@param ... any[] The arguments you wish to send
|
|
||||||
debugPrint(...)
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage
|
|
||||||
```lua
|
|
||||||
debugPrint('wow cool string to print', true, someOtherVar)
|
|
||||||
```
|
|
||||||
|
|
||||||
### React Utils
|
|
||||||
|
|
||||||
Signatures are not included for these utilities as the type definitions
|
|
||||||
are sufficient enough.
|
|
||||||
|
|
||||||
**useNuiEvent**
|
|
||||||
|
|
||||||
This is a custom React hook that is designed to intercept and handle
|
|
||||||
messages dispatched by the game scripts. This is the primary
|
|
||||||
way of creating passive listeners.
|
|
||||||
|
|
||||||
|
|
||||||
*Note: For now handlers can only be registered a single time. I haven't
|
|
||||||
come across a personal usecase for a cascading event system*
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
```jsx
|
|
||||||
const MyComp: React.FC = () => {
|
|
||||||
const [state, setState] = useState('')
|
|
||||||
|
|
||||||
useNuiEvent<string>('myAction', (data) => {
|
|
||||||
// the first argument to the handler function
|
|
||||||
// is the data argument sent using SendReactMessage
|
|
||||||
|
|
||||||
// do whatever logic u want here
|
|
||||||
setState(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
<h1>Some component</h1>
|
|
||||||
<p>{state}</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
Config.Debug = true -- print log
|
||||||
|
Config.Dev = false
|
||||||
|
Config.Nui = true -- will wait NUI trigger RegisterNUICallback('AppReady', ...) before init
|
||||||
```
|
```
|
||||||
|
|
||||||
**fetchNui**
|
# Hook method
|
||||||
|
|
||||||
This is a simple NUI focused wrapper around the standard `fetch` API.
|
```lua
|
||||||
This is the main way to accomplish active NUI data fetching
|
impl:HookMethod(methodName, hookFunc) --hookFunc must return args from reciver
|
||||||
or to trigger NUI callbacks in the game scripts.
|
|
||||||
|
|
||||||
When using this, you must always at least callback using `{}`
|
|
||||||
in the gamescripts.
|
|
||||||
|
|
||||||
*This can be heavily customized to your use case*
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
```ts
|
|
||||||
// First argument is the callback event name.
|
|
||||||
fetchNui<ReturnData>('getClientData').then(retData => {
|
|
||||||
console.log('Got return data from client scripts:')
|
|
||||||
console.dir(retData)
|
|
||||||
setClientData(retData)
|
|
||||||
}).catch(e => {
|
|
||||||
console.error('Setting mock data due to error', e)
|
|
||||||
setClientData({ x: 500, y: 300, z: 200})
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**debugData**
|
## example:
|
||||||
|
|
||||||
This is a function allowing for mocking dispatched game script
|
```lua
|
||||||
actions in a browser environment. It will trigger `useNuiEvent` handlers
|
local hud = main:GetImpl("Hud")
|
||||||
as if they were dispatched by the game scripts. **It will only fire if the current
|
hud:HookMethod("ShowHUD", function(this, ...)
|
||||||
environment is a regular browser and not CEF**
|
main:LogInfo("ShowHud was called")
|
||||||
|
return(...)
|
||||||
**Usage**
|
end)
|
||||||
```ts
|
|
||||||
// This will target the useNuiEvent hooks registered with `setVisible`
|
|
||||||
// and pass them the data of `true`
|
|
||||||
debugData([
|
|
||||||
{
|
|
||||||
action: 'setVisible',
|
|
||||||
data: true,
|
|
||||||
}
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Misc Utils**
|
# Export all method from all modules
|
||||||
|
|
||||||
These are small but useful included utilities.
|
```lua
|
||||||
|
exports['lr_addon']:ImplCall(name, func, ...) --Call a method in module external
|
||||||
* `isEnvBrowser()` - Will return a boolean indicating if the current
|
|
||||||
environment is a regular browser. (Useful for logic in development)
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
This boilerplate was designed with development workflow in mind.
|
|
||||||
It includes some helpful scripts to accomplish that.
|
|
||||||
|
|
||||||
**Hot Builds In-Game**
|
|
||||||
|
|
||||||
When developing in-game, you can use the hot build system by
|
|
||||||
running the `start:game` script. This is essentially the start
|
|
||||||
script but it writes to disk. Meaning all that is required is a
|
|
||||||
resource restart to update the game script
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
```sh
|
|
||||||
# yarn
|
|
||||||
yarn start:game
|
|
||||||
# npm
|
|
||||||
npm run start:game
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Production Builds**
|
# Main
|
||||||
|
|
||||||
When you are done with development phase for your resource. You
|
```lua
|
||||||
must create a production build that is optimized and minimized.
|
variable `main` is global
|
||||||
|
you can use this variable in anywhere
|
||||||
You can do this by running the following:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
yarn build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional Notes
|
- Properties
|
||||||
|
|
||||||
Need further support? Join our [Discord](https://discord.com/invite/HYwBjTbAY5)!
|
```lua
|
||||||
|
main.playerId
|
||||||
|
main.playerPed
|
||||||
|
main.playerCoords
|
||||||
|
main.playerHeading
|
||||||
|
main.playerServerId
|
||||||
|
```
|
||||||
|
|
||||||
|
- methods (internal use)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
main:GetImpl(implName) --Get module instance
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
main:ImplCall(implName, methodName, ...args) --Call a method in module
|
||||||
|
--You can also use this way
|
||||||
|
local testImpl = main:GetImpl("Test")
|
||||||
|
testImpl:<methodName>(...args)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Impl
|
||||||
|
|
||||||
|
- default methods
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:GetName() --Get name of module
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:OnReady() --Called when module was initialized
|
||||||
|
--Example
|
||||||
|
local Impl = NewImpl("Test")
|
||||||
|
|
||||||
|
function Impl:OnReady()
|
||||||
|
self:LogInfo("%s initialized", self:GetName())
|
||||||
|
--will print out: [INFO] [TEST] Test initialized
|
||||||
|
--Your rest of script
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:HookHere(value)
|
||||||
|
return value + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:ReplaceThis(a, b)
|
||||||
|
return a + b
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:OnDestroy() --Called when module start destroying (when hot reload module)
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:HookMethod(method, hookFn) --Hook a function at start of method. Must return value same as arguments of method
|
||||||
|
--Example
|
||||||
|
|
||||||
|
local testImpl = main:GetImpl("Test")
|
||||||
|
print(testImpl:HookHere(2))
|
||||||
|
--print out: 3
|
||||||
|
|
||||||
|
testImpl:HookMethod("HookHere", function(this, value)
|
||||||
|
print("Hook called");
|
||||||
|
return value
|
||||||
|
end)
|
||||||
|
print(testImpl:HookHere(2))
|
||||||
|
--print out: Hook called
|
||||||
|
--print out: 3
|
||||||
|
|
||||||
|
testImpl:HookMethod("HookHere", function(this, value)
|
||||||
|
print("Hook called 2");
|
||||||
|
return value + 1
|
||||||
|
end)
|
||||||
|
print(testImpl:HookHere(2))
|
||||||
|
--print out: Hook called 2
|
||||||
|
--print out: Hook called
|
||||||
|
--print out: 4 (because we was modified value = value + 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:ReplaceMethod(method, newMethod) --Replace method with new function
|
||||||
|
--Example
|
||||||
|
|
||||||
|
local testImpl = main:GetImpl("Test")
|
||||||
|
print(testImpl:ReplaceThis(2, 3))
|
||||||
|
--print out: 5
|
||||||
|
|
||||||
|
testImpl:ReplaceMethod("ReplaceThis", function(this, a, b)
|
||||||
|
return a * b
|
||||||
|
end)
|
||||||
|
print(testImpl:ReplaceThis(2, 3))
|
||||||
|
--print out: 6
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:RefreshMethod(method) --Refresh method to original
|
||||||
|
--Example
|
||||||
|
|
||||||
|
local testImpl = main:GetImpl("Test")
|
||||||
|
|
||||||
|
testImpl:RefreshMethod("HookHere")
|
||||||
|
print(testImpl:HookHere(2))
|
||||||
|
--print out: 3
|
||||||
|
|
||||||
|
testImpl:RefreshMethod("ReplaceThis")
|
||||||
|
print(testImpl:ReplaceThis(2, 3))
|
||||||
|
--print out: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:LogInfo(msg, ...) --Print log when Config.Debug == true
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:LogError(msg, ...) --Print log when Config.Debug == true
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:LogSuccess(msg, ...) --Print log when Config.Debug == true
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:LogWarning(msg, ...) --Print log when Config.Debug == true
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
Impl:Destroy() --Destroy module (Called when hot reload module)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Impl default properties
|
||||||
|
|
||||||
|
```lua
|
||||||
|
self.destroyed = false
|
||||||
|
self.originalMethods = {}
|
||||||
|
self.eventHandlers = {}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Create Impl
|
||||||
|
|
||||||
|
#### Module name must be the same as file name
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local Impl = NewImpl("Test2")
|
||||||
|
--file name must be Test2.impl.lua
|
||||||
|
--Place file in client/impl for clientside and server/impl for serverside
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local Impl = NewImpl("Test")
|
||||||
|
|
||||||
|
function Impl:OnReady()
|
||||||
|
-- Entry of module
|
||||||
|
self.testVar = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:GetData()
|
||||||
|
return self.data
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:Add(amount, amount2)
|
||||||
|
self.testVar = self.testVar + amount + amount2
|
||||||
|
return self.testVar
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
|
||||||
|
```lua
|
||||||
|
reload:<resourcename> <implname> <mode>
|
||||||
|
--Used for hot reload a module
|
||||||
|
--mode: 0 [default] (reload server and client) | 1 (reload only client) | 2 (reload only server)
|
||||||
|
--Example:
|
||||||
|
reload:lr_boilerplate Test --for reload module `Test` clientside and serverside
|
||||||
|
reload:lr_boilerplate Test 1 --for reload module `Test` clientside
|
||||||
|
reload:lr_boilerplate Test 2 --for reload module `Test` serverside
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
toggledebug:<resourcename>
|
||||||
|
--Used for toggle debug mode [modify variable Config.Debug] (print out log ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
toggledev:<resourcename>
|
||||||
|
--Used for toggle dev mode [modify variable Config.Dev]
|
||||||
|
```
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
Config = {}
|
Config = {}
|
||||||
|
Config.EnableModules = {
|
||||||
|
["Newbie"] = false,
|
||||||
|
["Test"] = true,
|
||||||
|
["Test2"] = true,
|
||||||
|
}
|
||||||
Config.Debug = true
|
Config.Debug = true
|
||||||
|
Config.Nui = false
|
|
@ -22,17 +22,17 @@ games {
|
||||||
shared_scripts {
|
shared_scripts {
|
||||||
'@ox_lib/init.lua',
|
'@ox_lib/init.lua',
|
||||||
'@es_extended/imports.lua',
|
'@es_extended/imports.lua',
|
||||||
|
"config.lua",
|
||||||
"main.lua",
|
"main.lua",
|
||||||
"impl.lua",
|
"impl.lua",
|
||||||
"config.lua",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_page 'web/build/index.html'
|
ui_page 'web/build/index.html'
|
||||||
|
|
||||||
client_scripts {
|
--[[ client_scripts {
|
||||||
"client/classes/*",
|
"client/classes/*",
|
||||||
"client/impl/*"
|
"client/impl/*"
|
||||||
}
|
} ]]
|
||||||
server_script {
|
server_script {
|
||||||
'@oxmysql/lib/MySQL.lua',
|
'@oxmysql/lib/MySQL.lua',
|
||||||
"server/classes/*",
|
"server/classes/*",
|
||||||
|
|
107
impl.lua
107
impl.lua
|
@ -1,5 +1,5 @@
|
||||||
Class = {}
|
Class = {}
|
||||||
|
env = IsDuplicityVersion() and "sv" or "cl"
|
||||||
-- default (empty) constructor
|
-- default (empty) constructor
|
||||||
function Class:Init(...) end
|
function Class:Init(...) end
|
||||||
|
|
||||||
|
@ -92,7 +92,8 @@ end
|
||||||
function Class:new(...)
|
function Class:new(...)
|
||||||
local obj = self:extend({
|
local obj = self:extend({
|
||||||
destroyed = false,
|
destroyed = false,
|
||||||
originalMethods = {}
|
originalMethods = {},
|
||||||
|
eventHandlers = {}
|
||||||
})
|
})
|
||||||
if obj.Init then obj:Init(...) end
|
if obj.Init then obj:Init(...) end
|
||||||
return obj
|
return obj
|
||||||
|
@ -131,7 +132,6 @@ function Impl:HookMethod(method, hookFn)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local result = {pcall(hookFn, ...)}
|
local result = {pcall(hookFn, ...)}
|
||||||
print(json.encode(result))
|
|
||||||
local success = table.remove(result, 1)
|
local success = table.remove(result, 1)
|
||||||
if not success then
|
if not success then
|
||||||
main:LogError("Impl %s hook %s error: %s", self.name, method, result[2])
|
main:LogError("Impl %s hook %s error: %s", self.name, method, result[2])
|
||||||
|
@ -165,6 +165,107 @@ function Impl:RefreshMethod(method)
|
||||||
self[method] = self.originalMethods[method]
|
self[method] = self.originalMethods[method]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Impl:RegisterCallback(name, cb)
|
||||||
|
lib.callback.register(("%s_%s:%s"):format(self.name, env, name), cb)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:On(name, ...)
|
||||||
|
if self.eventHandlers[name] then
|
||||||
|
return main:LogError("Event %s:%s already registered", self.name, name)
|
||||||
|
end
|
||||||
|
local handler = AddEventHandler(("%s_%s:%s"):format(self.name, env, name), ...)
|
||||||
|
self.eventHandlers[name] = handler
|
||||||
|
return handler
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:OnNet(name, ...)
|
||||||
|
if self.eventHandlers[name] then
|
||||||
|
return main:LogError("Event %s:%s already registered", self.name, name)
|
||||||
|
end
|
||||||
|
local handler = RegisterNetEvent(("%s_%s:%s"):format(self.name, env, name), ...)
|
||||||
|
self.eventHandlers[name] = handler
|
||||||
|
return handler
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:Off(name, handler)
|
||||||
|
if self.eventHandlers[name] then
|
||||||
|
RemoveEventHandler(self.eventHandlers[name])
|
||||||
|
self.eventHandlers[name] = nil
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
main:LogError("Event %s:%s not registered", self.name, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if env == 'sv' then
|
||||||
|
function Impl:Callback(impl, name, source, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
if not source then return main:LogError("param source missing") end
|
||||||
|
return lib.callback.await(("%s_%s:%s"):format(impl, "cl", name), source, ...)
|
||||||
|
end
|
||||||
|
function Impl:EmitNet(impl, name, source, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
if not source then return main:LogError("param source missing") end
|
||||||
|
return TriggerClientEvent(("%s_%s:%s"):format(impl, "cl", name), source, ...)
|
||||||
|
end
|
||||||
|
function Impl:Emit(impl, name, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
return TriggerEvent(("%s_%s:%s"):format(impl, "sv", name), ...)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function Impl:Callback(impl, name, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
return lib.callback.await(("%s_%s:%s"):format(impl, "sv", name), false, ...)
|
||||||
|
end
|
||||||
|
function Impl:Emit(impl, name, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
return TriggerEvent(("%s_%s:%s"):format(impl, "cl", name), ...)
|
||||||
|
end
|
||||||
|
function Impl:EmitNet(impl, name, ...)
|
||||||
|
if type(impl) == "object" then
|
||||||
|
impl = impl:GetName()
|
||||||
|
end
|
||||||
|
if not impl then return main:LogError("param impl missing") end
|
||||||
|
if not name then return main:LogError("param name missing") end
|
||||||
|
return TriggerServerEvent(("%s_%s:%s"):format(impl, "sv", name), ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:LogInfo(msg, ...)
|
||||||
|
main:LogInfo("[^6"..self.name.."^0] "..msg, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:LogError(msg, ...)
|
||||||
|
main:LogError("[^6"..self.name.."^0] "..msg, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:LogSuccess(msg, ...)
|
||||||
|
main:LogSuccess("[^6"..self.name.."^0] "..msg, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Impl:LogWarning(msg, ...)
|
||||||
|
main:LogWarning("[^6"..self.name.."^0] "..msg, ...)
|
||||||
|
end
|
||||||
|
|
||||||
function NewImpl(name)
|
function NewImpl(name)
|
||||||
local impl = Impl:extend({
|
local impl = Impl:extend({
|
||||||
name = name
|
name = name
|
||||||
|
|
171
main.lua
171
main.lua
|
@ -1,8 +1,15 @@
|
||||||
Main = {}
|
Main = {}
|
||||||
|
ResourceName = GetCurrentResourceName()
|
||||||
|
local RegisteredEvents = {}
|
||||||
if IsDuplicityVersion() then
|
if IsDuplicityVersion() then
|
||||||
function GetGameTimer()
|
function GetGameTimer()
|
||||||
return os.clock() * 1000
|
return os.clock() * 1000
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
RegisterNUICallback('AppReady', function(data, cb)
|
||||||
|
cb({})
|
||||||
|
NuiReady = true
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
function Main:Init()
|
function Main:Init()
|
||||||
local o = {}
|
local o = {}
|
||||||
|
@ -16,9 +23,31 @@ function Main:Init()
|
||||||
o.playerPed = PlayerPedId()
|
o.playerPed = PlayerPedId()
|
||||||
o.playerCoords = GetEntityCoords(o.playerPed)
|
o.playerCoords = GetEntityCoords(o.playerPed)
|
||||||
o.playerHeading = GetEntityHeading(o.playerPed)
|
o.playerHeading = GetEntityHeading(o.playerPed)
|
||||||
|
o.playerServerId = GetPlayerServerId(o.playerId)
|
||||||
o:Thread1()
|
o:Thread1()
|
||||||
|
|
||||||
|
else
|
||||||
|
o.ClientImpls = {}
|
||||||
|
for k, v in pairs(Config.EnableModules) do
|
||||||
|
if v then
|
||||||
|
local path = "client/impl/" .. k .. ".impl.lua"
|
||||||
|
local source = LoadResourceFile(ResourceName, path)
|
||||||
|
if source == nil then
|
||||||
|
self:LogError("Failed to load %s", path)
|
||||||
|
else
|
||||||
|
--[[ self:LogInfo("Loading %s", path)
|
||||||
|
self:LogInfo("Loaded %s", source) ]]
|
||||||
|
o.ClientImpls[k] = source
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lib.callback.register(ResourceName..":getClientImpl", function(source, implName)
|
||||||
|
return o.ClientImpls[implName]
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
o:Exports()
|
o:Exports()
|
||||||
|
o:RegisterCommands()
|
||||||
|
o:RegisterEvents()
|
||||||
return o
|
return o
|
||||||
end
|
end
|
||||||
if not IsDuplicityVersion() then
|
if not IsDuplicityVersion() then
|
||||||
|
@ -35,6 +64,76 @@ if not IsDuplicityVersion() then
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Main:RegisterCommands()
|
||||||
|
if not IsDuplicityVersion() then
|
||||||
|
RegisterCommand("toggledebug:"..ResourceName, function(source, args, rawCommand)
|
||||||
|
Config.Debug = not Config.Debug
|
||||||
|
self:LogInfo("Debug %s", Config.Debug)
|
||||||
|
end)
|
||||||
|
RegisterCommand("toggledev:"..ResourceName, function(source, args, rawCommand)
|
||||||
|
Config.Dev = not Config.Dev
|
||||||
|
self:LogInfo("Dev %s", Config.Dev)
|
||||||
|
SendNUIMessage({
|
||||||
|
action = "updateServerState",
|
||||||
|
data = {
|
||||||
|
isDev = Config.Dev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
RegisterCommand("implinfo:"..ResourceName, function(source, args, rawCommand)
|
||||||
|
self:ImplInfo()
|
||||||
|
end)
|
||||||
|
RegisterCommand("test", function()
|
||||||
|
TriggerEvent("test")
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
RegisterCommand("reload:"..ResourceName, function(source, args, rawCommand)
|
||||||
|
local implName = args[1]
|
||||||
|
local mode = args[2]
|
||||||
|
if mode == nil then
|
||||||
|
mode = "0"
|
||||||
|
end
|
||||||
|
self:LogInfo("Restarting impl: %s | side: %s (0: both, 1: client, 2: server)", implName, mode)
|
||||||
|
if mode == "0" or mode == "2" then
|
||||||
|
local svImpl = self:GetImpl(implName)
|
||||||
|
if svImpl then
|
||||||
|
svImpl:Destroy()
|
||||||
|
self.impls[implName] = nil
|
||||||
|
self.initializedImpls[implName] = nil
|
||||||
|
end
|
||||||
|
local source = LoadResourceFile(ResourceName, "server/impl/" .. implName .. ".impl.lua")
|
||||||
|
if source == nil then
|
||||||
|
self:LogError("Failed to load %s", path)
|
||||||
|
else
|
||||||
|
self:LogInfo("Loading %s", implName)
|
||||||
|
load(source)()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if mode == "0" or mode == "1" then
|
||||||
|
local clSource = LoadResourceFile(ResourceName, "client/impl/" .. implName .. ".impl.lua")
|
||||||
|
if clSource == nil then
|
||||||
|
self:LogError("Failed to load %s", path)
|
||||||
|
else
|
||||||
|
self:LogInfo("Loading %s", "client/impl/" .. implName .. ".impl.lua")
|
||||||
|
TriggerClientEvent(ResourceName..":restartClientImpl", -1, implName, clSource)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Main:RegisterEvents()
|
||||||
|
RegisterNetEvent(ResourceName..":restartClientImpl", function(implName, source)
|
||||||
|
local clImpl = self:GetImpl(implName)
|
||||||
|
if clImpl then
|
||||||
|
clImpl:Destroy()
|
||||||
|
self.impls[implName] = nil
|
||||||
|
self.initializedImpls[implName] = nil
|
||||||
|
end
|
||||||
|
load(source)()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
function Main:LogError(msg, ...)
|
function Main:LogError(msg, ...)
|
||||||
if not Config.Debug then return end
|
if not Config.Debug then return end
|
||||||
print(("[^1ERROR^0] " .. msg):format(...))
|
print(("[^1ERROR^0] " .. msg):format(...))
|
||||||
|
@ -68,6 +167,10 @@ function Main:CheckValidImpl(name, impl)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Main:RegisterImpl(name, impl)
|
function Main:RegisterImpl(name, impl)
|
||||||
|
if not Config.EnableModules[name] then
|
||||||
|
self:LogWarning("Impl %s not enabled", name)
|
||||||
|
return
|
||||||
|
end
|
||||||
if self.impls[name] then
|
if self.impls[name] then
|
||||||
self:LogWarning("Impl %s already registered", name)
|
self:LogWarning("Impl %s already registered", name)
|
||||||
return
|
return
|
||||||
|
@ -77,14 +180,34 @@ function Main:RegisterImpl(name, impl)
|
||||||
end
|
end
|
||||||
self.impls[name] = impl
|
self.impls[name] = impl
|
||||||
self.lastTimeImplRegistered = GetGameTimer()
|
self.lastTimeImplRegistered = GetGameTimer()
|
||||||
|
|
||||||
self:LogSuccess("Impl %s registered", name)
|
self:LogSuccess("Impl %s registered", name)
|
||||||
if self.ready then
|
if self.ready then
|
||||||
self.initializedImpls[name] = impl(self)
|
Citizen.CreateThread(function()
|
||||||
self.initializedImpls[name]:OnReady()
|
self:LogSuccess("Impl %s hot reloading", name)
|
||||||
|
Wait(1000)
|
||||||
|
self.initializedImpls[name] = impl(self)
|
||||||
|
if not self.initializedImpls[name] then
|
||||||
|
self:LogError("Impl %s failed to hot reload", name)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.initializedImpls[name]:OnReady()
|
||||||
|
self:LogSuccess("Impl %s hot reloaded", name)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Main:InitImpl()
|
function Main:InitImpl()
|
||||||
|
if not IsDuplicityVersion() then
|
||||||
|
for k, v in pairs(Config.EnableModules) do
|
||||||
|
if v then
|
||||||
|
self:LogInfo("Loading %s", k)
|
||||||
|
local source = lib.callback.await(ResourceName..":getClientImpl", false, k)
|
||||||
|
self:LogInfo("Loaded %s", k)
|
||||||
|
load(source)()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
for name, impl in pairs(self.impls) do
|
for name, impl in pairs(self.impls) do
|
||||||
self.initializedImpls[name] = impl(self)
|
self.initializedImpls[name] = impl(self)
|
||||||
end
|
end
|
||||||
|
@ -93,6 +216,15 @@ function Main:InitImpl()
|
||||||
for name, impl in pairs(self.initializedImpls) do
|
for name, impl in pairs(self.initializedImpls) do
|
||||||
impl:OnReady()
|
impl:OnReady()
|
||||||
end
|
end
|
||||||
|
if not IsDuplicityVersion() then
|
||||||
|
SendNUIMessage({
|
||||||
|
action = "updateServerState",
|
||||||
|
data = {
|
||||||
|
isDev = Config.Dev,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Main:GetImpl(name)
|
function Main:GetImpl(name)
|
||||||
|
@ -116,10 +248,9 @@ function Main:ImplCall(name, func, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function Main:ImplInfo()
|
function Main:ImplInfo()
|
||||||
for name, impl in pairs(self.impls) do
|
for name, impl in pairs(self.impls) do
|
||||||
local debug = debug.getinfo(impl.Init, "S")
|
local debug = debug.getinfo(impl.OnReady, "S")
|
||||||
self:LogInfo("Impl %s - %s", name, debug.short_src)
|
self:LogInfo("Impl %s - %s", name, debug.short_src)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -132,11 +263,41 @@ end
|
||||||
|
|
||||||
main = Main:Init()
|
main = Main:Init()
|
||||||
|
|
||||||
|
local origAddEventHandler = AddEventHandler
|
||||||
|
function AddEventHandler(eventName, ...)
|
||||||
|
if RegisteredEvents[eventName] then
|
||||||
|
main:LogWarning("Event %s already registered. Removing", eventName)
|
||||||
|
RemoveEventHandler(RegisteredEvents[eventName])
|
||||||
|
end
|
||||||
|
RegisteredEvents[eventName] = origAddEventHandler(eventName, ...)
|
||||||
|
return RegisteredEvents[eventName]
|
||||||
|
end
|
||||||
|
|
||||||
|
local origRegisterNetEvent = RegisterNetEvent
|
||||||
|
function RegisterNetEvent(eventName, ...)
|
||||||
|
if RegisteredEvents[eventName] then
|
||||||
|
main:LogWarning("Event %s already registered. Removing", eventName)
|
||||||
|
RemoveEventHandler(RegisteredEvents[eventName])
|
||||||
|
end
|
||||||
|
RegisteredEvents[eventName] = origRegisterNetEvent(eventName, ...)
|
||||||
|
return RegisteredEvents[eventName]
|
||||||
|
end
|
||||||
|
|
||||||
Citizen.CreateThread(function()
|
Citizen.CreateThread(function()
|
||||||
|
|
||||||
while GetGameTimer() < main.lastTimeImplRegistered + 1000 do
|
while GetGameTimer() < main.lastTimeImplRegistered + 1000 do
|
||||||
Citizen.Wait(0)
|
Citizen.Wait(0)
|
||||||
end
|
end
|
||||||
|
while ESX == nil do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
if not IsDuplicityVersion() then
|
||||||
|
while not ESX.IsPlayerLoaded() do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
while not NuiReady and Config.Nui do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
main:InitImpl()
|
main:InitImpl()
|
||||||
main:ImplInfo()
|
|
||||||
end)
|
end)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user