z
This commit is contained in:
parent
7f16dedc09
commit
bf1221578d
389
README.md
389
README.md
|
@ -1,19 +1,9 @@
|
|||
<div align="center">
|
||||
<img href="https://projecterror.dev" width="150" src="https://i.tasoagc.dev/c1pD" alt="Material-UI logo" />
|
||||
</div>
|
||||
<h1 align="center">FiveM React and Lua Boilerplate</h1>
|
||||
<p align="center">
|
||||
<a href="" rel="noopener">
|
||||
<img width=200px height=200px src="https://lorraxs.com/logo.svg" alt="Project logo"></a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
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>
|
||||
<h1 align="center">LR Fivem lua module</h1>
|
||||
|
||||
This repository is a basic boilerplate for getting started
|
||||
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.
|
||||
|
||||
## 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
|
||||
know this yet, React might not be for you just yet.*
|
||||
- [Node > v10.6](https://nodejs.org/en/)
|
||||
- [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
|
||||
|
||||
|
@ -40,177 +32,244 @@ it within your `resources` folder
|
|||
|
||||
### Installation
|
||||
|
||||
*The boilerplate was made using `yarn` but is still compatible with
|
||||
`npm`.*
|
||||
_The boilerplate was made using `yarn` but is still compatible with
|
||||
`npm`._
|
||||
|
||||
Install dependencies by navigating to the `web` folder within
|
||||
a terminal of your choice and type `npm i` or `yarn`.
|
||||
|
||||
## 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
|
||||
---@param action string The action you wish to target
|
||||
---@param data any The data you wish to send along with this action
|
||||
SendReactMessage(action, data)
|
||||
```
|
||||
|
||||
Usage
|
||||
```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.EnableModules = {
|
||||
["Newbie"] = false,
|
||||
["DamageLog"] = true,
|
||||
["Hud"] = true,
|
||||
['Player'] = true,
|
||||
['Speedometer'] = true
|
||||
}
|
||||
Config.Debug = true -- print log
|
||||
Config.Dev = false
|
||||
Config.Nui = true -- will wait NUI trigger RegisterNUICallback('AppReady', ...) before init
|
||||
```
|
||||
|
||||
# Hook method
|
||||
|
||||
```lua
|
||||
impl:HookMethod(methodName, hookFunc) --hookFunc must return args from reciver
|
||||
```
|
||||
|
||||
## example:
|
||||
|
||||
```lua
|
||||
local hud = main:GetImpl("Hud")
|
||||
hud:HookMethod("ShowHUD", function(this, ...)
|
||||
main:LogInfo("ShowHud was called")
|
||||
return(...)
|
||||
end)
|
||||
```
|
||||
|
||||
# Export all method from all modules
|
||||
|
||||
```lua
|
||||
exports['lr_addon']:ImplCall(name, func, ...) --Call a method in module external
|
||||
```
|
||||
|
||||
# Main
|
||||
|
||||
```lua
|
||||
variable `main` is global
|
||||
you can use this variable in anywhere
|
||||
```
|
||||
|
||||
- Properties
|
||||
|
||||
```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
|
||||
|
||||
```
|
||||
|
||||
**fetchNui**
|
||||
|
||||
This is a simple NUI focused wrapper around the standard `fetch` API.
|
||||
This is the main way to accomplish active NUI data fetching
|
||||
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})
|
||||
})
|
||||
```lua
|
||||
Impl:OnDestroy() --Called when module start destroying (when hot reload module)
|
||||
```
|
||||
|
||||
**debugData**
|
||||
```lua
|
||||
Impl:HookMethod(method, hookFn) --Hook a function at start of method. Must return value same as arguments of method
|
||||
--Example
|
||||
|
||||
This is a function allowing for mocking dispatched game script
|
||||
actions in a browser environment. It will trigger `useNuiEvent` handlers
|
||||
as if they were dispatched by the game scripts. **It will only fire if the current
|
||||
environment is a regular browser and not CEF**
|
||||
local testImpl = main:GetImpl("Test")
|
||||
print(testImpl:HookHere(2))
|
||||
--print out: 3
|
||||
|
||||
**Usage**
|
||||
```ts
|
||||
// This will target the useNuiEvent hooks registered with `setVisible`
|
||||
// and pass them the data of `true`
|
||||
debugData([
|
||||
{
|
||||
action: 'setVisible',
|
||||
data: true,
|
||||
}
|
||||
])
|
||||
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)
|
||||
```
|
||||
|
||||
**Misc Utils**
|
||||
```lua
|
||||
Impl:ReplaceMethod(method, newMethod) --Replace method with new function
|
||||
--Example
|
||||
|
||||
These are small but useful included utilities.
|
||||
local testImpl = main:GetImpl("Test")
|
||||
print(testImpl:ReplaceThis(2, 3))
|
||||
--print out: 5
|
||||
|
||||
* `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
|
||||
testImpl:ReplaceMethod("ReplaceThis", function(this, a, b)
|
||||
return a * b
|
||||
end)
|
||||
print(testImpl:ReplaceThis(2, 3))
|
||||
--print out: 6
|
||||
```
|
||||
|
||||
**Production Builds**
|
||||
```lua
|
||||
Impl:RefreshMethod(method) --Refresh method to original
|
||||
--Example
|
||||
|
||||
When you are done with development phase for your resource. You
|
||||
must create a production build that is optimized and minimized.
|
||||
local testImpl = main:GetImpl("Test")
|
||||
|
||||
You can do this by running the following:
|
||||
testImpl:RefreshMethod("HookHere")
|
||||
print(testImpl:HookHere(2))
|
||||
--print out: 3
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
yarn build
|
||||
testImpl:RefreshMethod("ReplaceThis")
|
||||
print(testImpl:ReplaceThis(2, 3))
|
||||
--print out: 5
|
||||
```
|
||||
|
||||
## Additional Notes
|
||||
```lua
|
||||
Impl:LogInfo(msg, ...) --Print log when Config.Debug == true
|
||||
```
|
||||
|
||||
Need further support? Join our [Discord](https://discord.com/invite/HYwBjTbAY5)!
|
||||
```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.EnableModules = {
|
||||
["Newbie"] = false,
|
||||
["Test"] = true,
|
||||
["Test2"] = true,
|
||||
}
|
||||
Config.Debug = true
|
||||
Config.Nui = false
|
|
@ -22,17 +22,17 @@ games {
|
|||
shared_scripts {
|
||||
'@ox_lib/init.lua',
|
||||
'@es_extended/imports.lua',
|
||||
"config.lua",
|
||||
"main.lua",
|
||||
"impl.lua",
|
||||
"config.lua",
|
||||
}
|
||||
|
||||
ui_page 'web/build/index.html'
|
||||
|
||||
client_scripts {
|
||||
--[[ client_scripts {
|
||||
"client/classes/*",
|
||||
"client/impl/*"
|
||||
}
|
||||
} ]]
|
||||
server_script {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
"server/classes/*",
|
||||
|
|
107
impl.lua
107
impl.lua
|
@ -1,5 +1,5 @@
|
|||
Class = {}
|
||||
|
||||
env = IsDuplicityVersion() and "sv" or "cl"
|
||||
-- default (empty) constructor
|
||||
function Class:Init(...) end
|
||||
|
||||
|
@ -92,7 +92,8 @@ end
|
|||
function Class:new(...)
|
||||
local obj = self:extend({
|
||||
destroyed = false,
|
||||
originalMethods = {}
|
||||
originalMethods = {},
|
||||
eventHandlers = {}
|
||||
})
|
||||
if obj.Init then obj:Init(...) end
|
||||
return obj
|
||||
|
@ -131,7 +132,6 @@ function Impl:HookMethod(method, hookFn)
|
|||
return
|
||||
end
|
||||
local result = {pcall(hookFn, ...)}
|
||||
print(json.encode(result))
|
||||
local success = table.remove(result, 1)
|
||||
if not success then
|
||||
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]
|
||||
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)
|
||||
local impl = Impl:extend({
|
||||
name = name
|
||||
|
|
167
main.lua
167
main.lua
|
@ -1,8 +1,15 @@
|
|||
Main = {}
|
||||
ResourceName = GetCurrentResourceName()
|
||||
local RegisteredEvents = {}
|
||||
if IsDuplicityVersion() then
|
||||
function GetGameTimer()
|
||||
return os.clock() * 1000
|
||||
end
|
||||
else
|
||||
RegisterNUICallback('AppReady', function(data, cb)
|
||||
cb({})
|
||||
NuiReady = true
|
||||
end)
|
||||
end
|
||||
function Main:Init()
|
||||
local o = {}
|
||||
|
@ -16,9 +23,31 @@ function Main:Init()
|
|||
o.playerPed = PlayerPedId()
|
||||
o.playerCoords = GetEntityCoords(o.playerPed)
|
||||
o.playerHeading = GetEntityHeading(o.playerPed)
|
||||
o.playerServerId = GetPlayerServerId(o.playerId)
|
||||
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
|
||||
o:Exports()
|
||||
o:RegisterCommands()
|
||||
o:RegisterEvents()
|
||||
return o
|
||||
end
|
||||
if not IsDuplicityVersion() then
|
||||
|
@ -35,6 +64,76 @@ if not IsDuplicityVersion() then
|
|||
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, ...)
|
||||
if not Config.Debug then return end
|
||||
print(("[^1ERROR^0] " .. msg):format(...))
|
||||
|
@ -68,6 +167,10 @@ function Main:CheckValidImpl(name, impl)
|
|||
end
|
||||
|
||||
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
|
||||
self:LogWarning("Impl %s already registered", name)
|
||||
return
|
||||
|
@ -77,14 +180,34 @@ function Main:RegisterImpl(name, impl)
|
|||
end
|
||||
self.impls[name] = impl
|
||||
self.lastTimeImplRegistered = GetGameTimer()
|
||||
|
||||
self:LogSuccess("Impl %s registered", name)
|
||||
if self.ready then
|
||||
Citizen.CreateThread(function()
|
||||
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
|
||||
|
||||
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
|
||||
self.initializedImpls[name] = impl(self)
|
||||
end
|
||||
|
@ -93,6 +216,15 @@ function Main:InitImpl()
|
|||
for name, impl in pairs(self.initializedImpls) do
|
||||
impl:OnReady()
|
||||
end
|
||||
if not IsDuplicityVersion() then
|
||||
SendNUIMessage({
|
||||
action = "updateServerState",
|
||||
data = {
|
||||
isDev = Config.Dev,
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Main:GetImpl(name)
|
||||
|
@ -116,10 +248,9 @@ function Main:ImplCall(name, func, ...)
|
|||
end
|
||||
|
||||
|
||||
|
||||
function Main:ImplInfo()
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
@ -132,11 +263,41 @@ end
|
|||
|
||||
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()
|
||||
|
||||
while GetGameTimer() < main.lastTimeImplRegistered + 1000 do
|
||||
Citizen.Wait(0)
|
||||
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:ImplInfo()
|
||||
end)
|
||||
|
|
Loading…
Reference in New Issue
Block a user