FrameworkZ 10.8.3
Provides a framework for Project Zomboid with various systems.
Loading...
Searching...
No Matches
__Foundation.lua
Go to the documentation of this file.
1--[[ Documentation
2
3
4
5██████ ██████ ██████ ██ ██ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██
6██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
7██ ██ ██ ██ ██ ██ ██ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██
8██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
9██████ ██████ ██████ ██████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████
10
11
12
13--]]
14
15--! \mainpage Main Page
16--! Created By RJ_RayJay
17--! \section Introduction
18--! FrameworkZ is a roleplay framework for the game Project Zomboid. This framework is designed to be a base for roleplay servers, providing a variety of features and systems to help server owners create a unique and enjoyable roleplay experience for their players.
19--! \section Features
20--! FrameworkZ includes a variety of features and systems to help server owners create a unique and enjoyable roleplay experience for their players. Some of the features and systems include:
21--! - Characters
22--! - Factions
23--! - Entities
24--! - Items
25--! - Inventories
26--! - Modules
27--! - Plugins
28--! - Hooks
29--! - Notifications
30--! - ...and more!
31--! \section Installation
32--! To install the FrameworkZ framework, simply download the latest release from the Steam Workshop and add the Workshop ID/Mod ID into your Project Zomboid server's config file. After installing, you can start your server and the framework will be ready to use. Typically you would also install a gamemode alongside the framework for additional functionality. Refer to your gamemode of choice for additional installation instructions.
33--! \section Usage
34--! The FrameworkZ framework is designed to be easy to use and extend. The framework is built using Lua, a lightweight, multi-paradigm programming language designed primarily for embedded use in applications. The framework is designed to be modular, allowing server owners to easily add, remove, and modify features and systems to suit their needs. The framework also includes extensive documentation to help server owners understand how to use and extend the framework.
35--! \section Contributing
36--! The FrameworkZ framework is an open-source project and we welcome contributions from the community. If you would like to contribute to the framework, you can do so by forking the GitHub repository, making your changes, and submitting a pull request. We also welcome bug reports, feature requests, and feedback from the community. If you have any questions or need help with the framework, you can join the FrameworkZ Discord server and ask for assistance in the #support channel.
37--! \section License
38--! The FrameworkZ framework is licensed under the MIT License, a permissive open-source license that allows you to use, modify, and distribute the framework for free. You can find the full text of the MIT License in the LICENSE file included with the framework. We chose the MIT License because we believe in the power of open-source software and want to encourage collaboration and innovation in the Project Zomboid community.
39--! \section Support
40--! If you need help with the FrameworkZ framework, you can join the FrameworkZ Discord server and ask for assistance in the #support channel. We have a friendly and knowledgeable community that is always willing to help with any questions or issues you may have. We also have a variety of resources available to help you get started with the framework, including documentation, tutorials, and example code.
41--! \section Conclusion
42--! The FrameworkZ framework is a powerful and flexible tool for creating roleplay servers in Project Zomboid. Whether you are a server owner looking to create a unique roleplay experience for your players or a developer looking to contribute to an open-source project, the FrameworkZ framework has something for everyone. We hope you enjoy using the framework and look forward to seeing the amazing roleplay experiences you create with it.
43--! \section Links
44--! - Steam Workshop: Coming Soon(tm)
45--! - GitHub Repository: https://github.com/Project-Zomboid-FrameworkZ/Framework
46--! - Bug Reports: https://github.com/Project-Zomboid-FrameworkZ/Framework/issues
47--! - Discord Server: https://discord.gg/PgNTyva3xk
48--! - Documentation: https://frameworkz.projectzomboid.life/documentation/
49
50--! \page global_variables Global Variables
51--! \section FrameworkZ FrameworkZ
52--! FrameworkZ
53--! The global table that contains all of the framework.
54--! [table]: /variable_types.html#table "table"
55--! \page variable_types Variable Types
56--! \section string string
57--! A string is a sequence of characters. Strings are used to represent text and are enclosed in double quotes or single quotes.
58--! \section boolean boolean
59--! A boolean is a value that can be either true or false. Booleans are used to represent logical values.
60--! \section integer integer
61--! A integer is a numerical value without any decimal points.
62--! \section float float
63--! A float is a numerical value with decimal points.
64--! \section table table
65--! A table is a collection of key-value pairs. It is the only data structure available in Lua that allows you to store data with arbitrary keys and values. Tables are used to represent arrays, sets, records, and other data structures.
66--! \section function function
67--! A function is a block of code that can be called and executed. Functions are used to encapsulate and reuse code.
68--! \section nil nil
69--! Nil is a special value that represents the absence of a value. Nil is used to indicate that a variable has no value.
70--! \section any any
71--! Any is a placeholder that represents any type of value. It is used to indicate that a variable can hold any type of value.
72--! \section mixed mixed
73--! Mixed is a placeholder that represents a combination of different types of values. It is used to indicate that a variable can hold a variety of different types of values.
74--! \section multiple multiple
75--! Multiple is a placeholder that represents a list of values. It is used to indicate that a function can accept multiple arguments.
76--! \section class class
77--! Class is a placeholder that represents a class of objects by a table set to a metatable.
78--! \section object object
79--! Object is a placeholder that represents an instance of a class.
80
81--[[ Setup
82
83
84
85███████ ███████ ████████ ██ ██ ██████
86██ ██ ██ ██ ██ ██ ██
87███████ █████ ██ ██ ██ ██████
88 ██ ██ ██ ██ ██ ██
89███████ ███████ ██ ██████ ██
90
92
93--]]
94
95local Events = Events
96local getPlayer = getPlayer
97local isClient = isClient
98local isServer = isServer
99local ModData = ModData
100local unpack = unpack
101
102FrameworkZ = FrameworkZ or {}
103
104--! \brief Contains all of the User Interfaces for FrameworkZ.
105--! \class FrameworkZ.UI
106FrameworkZ.UI = FrameworkZ.UI or {}
108--! \brief Foundation for FrameworkZ.
109--! \class FrameworkZ.Foundation
110FrameworkZ.Foundation = {}
111--FrameworkZ.Foundation.__index = FrameworkZ.Foundation
112
113FrameworkZ.Foundation.Events = {}
115--! \brief Modules for FrameworkZ. Extends the framework with additional functionality.
116--! \class FrameworkZ.Foundation.Modules
117FrameworkZ.Foundation.Modules = {}
119--! \brief Create a new instance of the FrameworkZ framework.
120--! \return \table The new instance of the FrameworkZ framework.
121function FrameworkZ.Foundation.New()
122 return FrameworkZ:CreateObject(FrameworkZ.Foundation, "Foundation")
123end
124
125--! \brief Create a new module for the FrameworkZ framework.
126--! \param moduleObject \object The object to use as the module.
127--! \param moduleName \string The name of the module.
128--! \return \object The new module.
129function FrameworkZ.Foundation:NewModule(moduleObject, moduleName)
130 local object = FrameworkZ:CreateObject(moduleObject, moduleName)
132 self.Modules[moduleName] = object
133
134 return object
135end
137--! \brief Get a module by name.
138--! \param moduleName \string The name of the module.
139--! \return \object The module object or \false if the module was not found.
140function FrameworkZ.Foundation:GetModule(moduleName)
141 if not moduleName or moduleName == "" then return false, "No module name supplied." end
142 if not self.Modules[moduleName] then return false, "Module not found." end
143
144 return self.Modules[moduleName]
145end
147--! \brief Get a module's meta object stored on a module. Not every module will have a meta object. This is a very specific use case and is used for getting instantiable objects such as PLAYER objects or CHARACTER objects.
148--! \param moduleName \string The name of the module.
149--! \return \object The meta object stored on the module or false if nothing was found.
150function FrameworkZ.Foundation:GetModuleMetaObject(moduleName)
151 local module, message = self:GetModule(moduleName)
152 if not module then return false, message end
153 if not module.MetaObject then return false, "Module does not have a meta object." end
154
155 return module.MetaObject
156end
157
158--! \brief Register FrameworkZ. This is called after framework definition.
159function FrameworkZ.Foundation:RegisterFramework()
160 FrameworkZ.Foundation:RegisterFrameworkHandler()
161 FrameworkZ:RegisterObject(self)
162end
163
164--! \brief Register a module for FrameworkZ. This is called after module definition.
165--! \param module \object The module to register.
166function FrameworkZ.Foundation:RegisterModule(module)
167 FrameworkZ.Foundation:RegisterModuleHandler(module)
168 FrameworkZ:RegisterObject(module)
169end
170
171--! \brief Get the version of FrameworkZ Foundation.
172--! \return \string The version of the FrameworkZ Foundation.
173function FrameworkZ.Foundation:GetVersion()
174 return self.version
175end
176
177FrameworkZ.Foundation = FrameworkZ.Foundation.New()
178
179--[[ Networking
180
181
182
183███ ██ ███████ ████████ ██ ██ ██████ ██████ ██ ██ ██ ███ ██ ██████
184████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
185██ ██ ██ █████ ██ ██ █ ██ ██ ██ ██████ █████ ██ ██ ██ ██ ██ ███
186██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
187██ ████ ███████ ██ ███ ███ ██████ ██ ██ ██ ██ ██ ██ ████ ██████
188
189
190
191--]]
193--! \brief The name of the networking module for commands in OnClientCommand and OnServerCommand. Not to be confused with a FrameworkZ module.
194FrameworkZ.Foundation.NetworksName = "FZ_NETWORKS"
195
196--! \brief Pending confirmations for network requests. This is used to track requests that are waiting for a response.
197FrameworkZ.Foundation.PendingConfirmations = {}
198
199--! \brief Subscribers for the network system. This is used to track subscribers for channels.
200FrameworkZ.Foundation.Subscribers = {}
201
202--! \brief Meta data for the subscribers. This is used to track when a channel was created and when it was last fired.
203FrameworkZ.Foundation.SubscribersMeta = {}
204
205--! \brief Generate a time-based unique request ID for network requests.
206--! \return \string A unique request ID based on the current timestamp and a random number.
207local function generateRequestID()
208 return tostring(getTimestamp()) .. "-" .. tostring(ZombRand(100000, 999999))
209end
210
211--! \brief Convert a path to a string. This is used to convert a table path to a string path.
212--! \param path \string or \table The path to convert. If a string is supplied, it will be returned as is. If a table is supplied, it will be concatenated with dots.
213--! \return \string The string representation of the path.
214function FrameworkZ.Foundation:PathToString(path)
215 if type(path) == "string" then
216 return path
217 end
218
219 return table.concat(path, ".")
220end
221
222--! \brief Add a new channel to the network system. Channels are used to subscribe to; changes in values, or fire events.
223--! \param key \string or \table The key to use for the channel. Use a table to create nested channels. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
224function FrameworkZ.Foundation:AddChannel(key)
225 local stringKey = self:PathToString(key)
226
227 self.Subscribers[stringKey] = {}
228 self.SubscribersMeta[stringKey] = {
229 originalKey = key,
230 createdAt = getTimestamp(),
232 }
233end
234
235--! \brief Remove a channel from the network system. This will remove all subscribers and meta data for the channel.
236--! \param key \string or \table The key to use for the channel. Use a table to create nested channels. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
237function FrameworkZ.Foundation:RemoveChannel(key)
238 local stringKey = self:PathToString(key)
239
240 self.Subscribers[stringKey] = nil
241 self.SubscribersMeta[stringKey] = nil
242end
243
244--! \brief Get the channel for a key. This will return the channel data for the key.
245--! \param key \string or \table The key to use for the channel. Use a table to create nested channels. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
246--! \return \table The channel data for the key.
247function FrameworkZ.Foundation:GetChannel(key)
248 local stringKey = self:PathToString(key)
249
250 return self.Subscribers[stringKey]
251end
252
253--! \brief Get the meta data for a channel. This will return the meta data for the key.
254--! \param key \string or \table The key to use for the channel. Use a table to create nested channels. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
255--! \return \table The meta data for the key.
256function FrameworkZ.Foundation:GetChannelMeta(key)
257 local stringKey = self:PathToString(key)
258
259 return self.SubscribersMeta[stringKey]
260end
261
262--! \brief Check if a channel exists for a key. This will return true if the channel exists, false otherwise.
263--! \param key \string or \table The key to use for the channel. Use a table to create nested channels. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
264--! \return \boolean True if the channel exists, false otherwise.
265function FrameworkZ.Foundation:HasChannel(key)
266 local stringKey = self:PathToString(key)
268 return self.Subscribers[stringKey] ~= nil
269end
270
271--! \brief Log all channels and their subscribers to the console. This is useful for debugging and understanding the network system.
272function FrameworkZ.Foundation:LogChannels()
273 print("=== FrameworkZ.Networks Channels ===")
274
275 for key, subs in pairs(self.Subscribers) do
277 local createdString = meta and os.date("%X", meta.createdAt) or "?"
278 local firedString = meta and meta.lastFiredAt and os.date("%X", meta.lastFiredAt) or "never"
280 print("Channel:", key, "(created at " .. createdString .. ", last fired at " .. firedString .. ")")
281
282 for id, _ in pairs(subs) do
283 print(" ->", id)
284 end
285 end
286end
288--! \brief Subscribes to a key to listen for changes with the first three arguments supplied, or can be used for sending/receiving fire events with the first two arguments supplied.
289--! \param key \string The key to subscribe to. Use a \table to subscribe to nested values. \note Example key argument as a table: {"key", "subkey"} == _G["key"]["subkey"] or _G.key.subkey on lookup when subscribing.
290--! \param idOrCallback \string or \function The ID of the function callback being added, or the callback function itself. If a string is supplied, it will be used as the ID for the callback.
291--! \param maybeCallback \function The callback function to call when the key changes. This is optional if the first argument is a function.
292--! \return \function The callback function that was added. This can be used to unsubscribe later.
293function FrameworkZ.Foundation:Subscribe(key, idOrCallback, maybeCallback)
294 local id, callback
296 if type(idOrCallback) == "function" then
297 id = "__default"
298 callback = idOrCallback
299 else
300 id = idOrCallback
301 callback = maybeCallback
302 end
303
304 if not self:HasChannel(key) then
305 self:AddChannel(key)
306 end
307
308 local channel = self:GetChannel(key)
310 if not channel[id] then
311 channel[id] = {}
312 end
313
314 table.insert(channel[id], callback)
315
316 return callback
317end
318
319--! \brief Unsubscribes from a key. This will remove the callback from the channel.
320--! \param key \string The key to unsubscribe from. Use a \table to unsubscribe from nested values. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
321--! \param id \sting The ID of the function callback being removed. Default for fire events: "__default"
322function FrameworkZ.Foundation:Unsubscribe(key, id)
323 local subscribers = self:GetSubscribers(key)
324 if not subscribers then return false end
327end
328
329--! \brief Get the subscribers for a key. This will return the subscribers for the key.
330--! \param key \string The key to get the subscribers for. Use a \table to get the subscribers for nested values. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
331--! \return \table The subscribers for the key.
332function FrameworkZ.Foundation:GetSubscribers(key)
333 local channel = self:GetChannel(key)
334 local subscribers = {}
335
336 for k, v in pairs(channel) do
337 for k2, v2 in ipairs(v) do
338 table.insert(subscribers, v2)
339 end
340 end
341
343end
344
345--! \brief Check if a subscription exists for a key. This will return true if the subscription exists, false otherwise.
346--! \param key \string The key to check for. Use a \table to check for nested values. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
347--! \param id \string The ID of the function callback being checked.
348--! \return \boolean True if the subscription exists, false otherwise.
349function FrameworkZ.Foundation:HasSubscription(key, id)
350 local channel = self:GetChannel(key)
351
352 return channel and channel[id] ~= nil
353end
354
355--! \brief Fires a callback for a key. This will call the callback for the key with the value supplied.
356--! \param key \string The key to fire the callback for. Use a \table to fire the callback for nested values. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
357--! \param data \table The standard data to pass to the callback. Generally contains diagnostic information.
358--! \param arguments \table The values to pass to the callback. This can be any type of values stored in the table.
359--! \return \table A table of return values from the callbacks. The keys are the IDs of the callbacks and the values are the return values from the callbacks.
360function FrameworkZ.Foundation:Fire(key, data, arguments)
361 if not self:HasChannel(key) then
362 print("[FZ] Warning: Received fire event for unknown ID: ", key)
363 end
365 local returnValues = {}
366 local subscribers = self:GetSubscribers(key)
367
369 local results
370
371 for id, callback in ipairs(subscribers) do
372 results = FrameworkZ.Utilities:Pack(callback(data, FrameworkZ.Utilities:Unpack(arguments)))
375 end
376
377 local meta = self:GetChannelMeta(key)
378
379 if meta then
380 meta.lastFiredAt = getTimestamp()
381 end
382 end
384 return returnValues
385end
386
387--! \brief Subscribes and fires callback immediately if the value is already set. Useful for UIs.
388--! \param key \string or \table The key to watch. Use a table to watch nested values. \see FrameworkZ.Foundation::Subscribe for an example on how to supply a table as a key.
389--! \param id \string The ID of the function callback being added.
390--! \param callback \function The callback to call when the key changes.
391function FrameworkZ.Foundation:Watch(key, id, callback)
392 self:Subscribe(key, id, callback)
393
394 local value = self:GetNestedValue(_G, type(key) == "string" and {key} or key)
395
396 if value ~= nil then
398 end
399end
401--! \brief Sends a get request to the server.
402--! \param key \mixed The key to get. Does not support getting functions.
403--! \param callback \function The callback to call on the client when the server returns the value.
404--! \param callbackID \string The key to use for the callback on the server after getting the value.
405--! \param broadcast \boolean Whether to broadcast the get callback to all clients.
407 if type(key) == "string" then
409 end
410
411 local requestID = generateRequestID()
412
413 if callback then
415 end
416
417 sendClientCommand(self.NetworksName, "GetData", {
418 key = key,
419 broadcast = broadcast,
420 callbackID = callbackID,
421 requestID = requestID,
422 args = FrameworkZ.Utilities:Pack(...)
423 })
424
425 return requestID
426end
427
428--! \brief Sends a set request to the server.
429--! \param key \string or \table The key to set. Use a table to set nested values. \note Example key argument as a table: {"key", "subkey"} == _G["key"]["subkey"] or _G.key.subkey on lookup when setting.
430--! \param value \mixed The value to set. Does not support functions.
431--! \param callback \function The callback to call on the client when the server confirms the set.
432--! \param callbackID \string The key to use for the callback on the server after setting the value.
433--! \param broadcast \boolean Whether to broadcast the set callback to all clients.
435 if type(key) == "string" then
437 end
438
439 local requestID = generateRequestID()
440
441 if callback then
443 end
444
445 sendClientCommand(self.NetworksName, "SetData", {
446 key = key,
447 value = value,
448 broadcast = broadcast,
449 callbackID = callbackID,
450 requestID = requestID
451 })
452
453 return requestID
454end
455
456--! \brief Sends a fire event to the server or client. This is used to send events to subscribers.
457--! \param isoPlayer \object The player sending the fire event. If nil, the event will be fired but no confirmation will be sent back (send and forget).
458--! \param subscriptionID \string The ID of the subscription to fire. This is the key used to subscribe to the event. It's recommended to use a string matching your function's callback name in a unique way when adding a subscription.
459--! \param callback \function The callback to call when the server confirms the fire event. This is optional and can be nil if you don't need confirmation.
460--! \param ... \multiple Additional arguments of any amount to pass to the subscription. These can be any type of values except functions as they do not get networked.
461--! \return \string The request ID for the fire event. This can be used to track the request and get confirmation later.
462function FrameworkZ.Foundation:SendFire(isoPlayer, subscriptionID, callback, ...)
463 local playerID = isoPlayer and isoPlayer:getOnlineID() or nil
464 local requestID = generateRequestID()
465
466 if callback then
471 sentAt = getTimestamp()
473 end
474
475 if isClient() then
476 local payload = {
480 clientSentAt = getTimestamp()
482
483 sendClientCommand(isoPlayer, self.NetworksName, "SendFire", payload)
484 elseif isServer() then
485 local payload = {
490 serverSentAt = getTimestamp()
492
493 sendServerCommand(isoPlayer, self.NetworksName, "SendFire", payload)
494 end
496 return requestID
497end
498
499--! \brief Get a nested value from a table using a path. This is used to get values from nested tables.
500--! \param root \table The root table to get the value from.
501--! \param path \table The path to the value. This is a table of keys to traverse the nested tables.
502--! \return \mixed The value at the end of the path, or nil if the path does not exist.
503--! \note Example path argument: {"key", "subkey"} == root["key"]["subkey"]
504function FrameworkZ.Foundation:GetNestedValue(root, path)
505 local current = root
506
507 for _, key in ipairs(path) do
508 if type(current) ~= "table" then return nil end
510 end
512 return current
513end
515--! \brief Set a nested value in a table using a path. This is used to set values in nested tables.
516--! \param root \table The root table to set the value in.
517--! \param path \table The path to the value. This is a table of keys to traverse the nested tables.
518--! \param value \mixed The value to set at the end of the path.
519--! \return \mixed The value that was set at the end of the path.
520--! \note Example path argument: {"key", "subkey"} == root["key"]["subkey"] = value
521function FrameworkZ.Foundation:SetNestedValue(root, path, value)
522 local current = root
523
524 for i = 1, #path - 1 do
525 local key = path[i]
528 end
529
530 current[path[#path]] = value
531
532 return current[path[#path]]
533end
534
535if isServer() then
536
537 --! \brief Handles incoming commands from the client on the server.
538 --! \param module \string The name of the module that sent the command. This should match the NetworksName defined in FrameworkZ.Foundation.NetworksName.
539 --! \param command \string The command that was sent by the client.
540 --! \param isoPlayer \object The player that sent the command. This is the player object that sent the command.
541 --! \param arguments \table The arguments that were sent with the command. This contains the data needed to process the command.
542 --! \note This function is called on the server when a client sends a command to the server. It processes the command and sends a response back to the client using the networking system.
543 function FrameworkZ.Foundation:OnClientCommand(module, command, isoPlayer, arguments)
544 if module ~= self.NetworksName then return end
545
546 if command == "GetData" then
547 local key = arguments.key
548 local value = self:GetNestedValue(_G, key)
549 local returnValues = {}
551 if arguments.callbackID then
552 local callbacks = self:GetSubscribers(arguments.callbackID)
553
554 if callbacks then
555 for id, callback in pairs(callbacks) do
556 local returnValue = callback(isoPlayer, key, value, nil, arguments.args)
557
558 if returnValue then
559 if not returnValues[id] then returnValues[id] = {} end
560 returnValues[id] = returnValue
561 end
562 end
563 end
564 end
565
566 if not arguments.broadcast then
567 sendServerCommand(isoPlayer, self.NetworksName, "ReturnData", {
568 key = key,
569 value = value,
570 broadcast = false,
571 requestID = arguments.requestID,
572 returnValues = returnValues,
573 args = arguments.args
574 })
575 else
576 local onlineUsers = getOnlinePlayers()
577
578 for i = 0, onlineUsers:size() - 1 do
579 sendServerCommand(onlineUsers:get(i), self.NetworksName, "ReturnData", {
580 key = key,
581 value = value,
582 broadcast = true,
583 requestID = arguments.requestID,
585 args = arguments.args
586 })
587 end
588 end
589 elseif command == "SetData" then
590 local key = arguments.key
591 local value = arguments.value
592
593 self:SetNestedValue(_G, key, value)
595 local newValue = self:GetNestedValue(_G, key)
596
597 if value ~= newValue then
598 sendServerCommand(isoPlayer, self.NetworksName, "FailedSet", {
599 key = key,
600 value = newValue,
601 broadcast = false,
602 requestID = arguments.requestID
603 })
604
605 return
606 end
607
608 if arguments.callbackID then
609 local callbacks = self:GetSubscribers(arguments.callbackID)
610
611 if callbacks then
612 for _, callback in pairs(callbacks) do
614 end
615 end
616 end
617
618 if not arguments.broadcast then
619 sendServerCommand(isoPlayer, self.NetworksName, "ConfirmSet", {
620 key = key,
621 value = newValue,
622 broadcast = false,
623 requestID = arguments.requestID
624 })
625 else
626 local onlineUsers = getOnlinePlayers()
627
628 for i = 0, onlineUsers:size() - 1 do
629 sendServerCommand(onlineUsers:get(i), self.NetworksName, "ConfirmSet", {
631 value = newValue,
632 broadcast = true,
633 requestID = arguments.requestID
634 })
635 end
636 end
637 elseif command == "SendFire" then
638 local subID = arguments.subID
639 local meta = self:GetChannelMeta(subID) or {}
640 local data = {
642 clientSentAt = arguments.clientSentAt,
643 subID = subID,
644 subCreatedAt = meta.createdAt,
645 subLastFiredAt = meta.lastFiredAt
647
648 local returnValues = self:Fire(subID, data, arguments.args)
649
650 sendServerCommand(isoPlayer, self.NetworksName, "ConfirmFire", {
651 requestID = arguments.requestID,
653 meta = meta,
655 })
656 elseif command == "ConfirmFire" then
657 local confirmation = self.PendingConfirmations[arguments.requestID]
658
659 if confirmation then
660 local meta = arguments.meta or {}
661 local callback = confirmation.callback
662 local returnValues = arguments.returnValues or {}
663
664 for _, returnArgs in pairs(returnValues) do
665 local data = {
668 sentAt = confirmation.sentAt,
669 createdAt = meta.createdAt,
670 lastFiredAt = meta.lastFiredAt,
671 }
672
673 callback(data, FrameworkZ.Utilities:Unpack(returnArgs))
674 end
675
677 end
678 end
679 end
680end
681
682if isClient() then
683
684 --! \brief Handles incoming commands from the server on the client.
685 --! \param module \string The name of the module that sent the command. This should match the NetworksName defined in FrameworkZ.Foundation.NetworksName.
686 --! \param command \string The command that was sent by the server.
687 --! \param arguments \table The arguments that were sent with the command. This contains the data needed to process the command.
688 --! \note This function is called on the client when the server sends a command to the client. It processes the command and sends a response back to the server using the networking system.
689 function FrameworkZ.Foundation:OnServerCommand(module, command, arguments)
690 if module ~= self.NetworksName then return end
691
692 if command == "ConfirmSet" then
693 local requestID = arguments.requestID
695
696 if confirmation then
697 if arguments.value == confirmation.newValue then
699
700 if callback then
701 callback(arguments.key, arguments.value)
702 end
703 end
704
706 end
707
708 if arguments.broadcast then
709 local key = arguments.key
710 local callbacks = self:GetSubscribers(key)
711
712 if callbacks then
713 for _, callback in pairs(callbacks) do
714 callback(key, arguments.value)
715 end
716 end
717 end
718 elseif command == "ReturnData" then
719 local isoPlayer = getPlayer()
720 local requestID = arguments.requestID
722
723 if confirmation then
725
726 if callback then
727 callback(isoPlayer, arguments.key, arguments.value, arguments.returnValues, arguments.args)
728 end
729
731 end
733 if arguments.broadcast then
734 local key = arguments.key
735 local callbacks = self:GetSubscribers(key)
736
737 if callbacks then
738 for _, callback in pairs(callbacks) do
739 callback(isoPlayer, key, arguments.value, arguments.returnValues, arguments.args)
740 end
741 end
742 end
743 elseif command == "SendFire" then
744 local subID = arguments.subID
745 local isoPlayer = getSpecificPlayer(arguments.playerID)
746 local meta = self:GetChannelMeta(subID) or {}
747 local data = {
749 serverSentAt = arguments.serverSentAt,
750 subID = subID,
751 subCreatedAt = meta.createdAt,
752 subLastFiredAt = meta.lastFiredAt
753 }
754
755 local returnValues = self:Fire(subID, data, arguments.args)
756
757 sendClientCommand(isoPlayer, self.NetworksName, "ConfirmFire", {
758 requestID = arguments.requestID,
759 subID = subID,
760 meta = meta,
762 })
763 elseif command == "ConfirmFire" then
764 local confirmation = self.PendingConfirmations[arguments.requestID]
765
766 if confirmation then
767 local meta = arguments.meta or {}
768 local callback = confirmation.callback
769 local returnValues = arguments.returnValues or {}
770
771 for _, returnArgs in pairs(returnValues) do
772 local data = {
775 sentAt = confirmation.sentAt,
776 createdAt = meta.createdAt,
777 lastFiredAt = meta.lastFiredAt,
778 }
779
780 callback(data, FrameworkZ.Utilities:Unpack(returnArgs))
781 end
782
784 end
785 elseif command == "FailedSet" then
786 local requestID = arguments.requestID
788
789 if confirmation then
791 print("[FZ] Failed to set value for key: " .. table.concat(arguments.key, ".") .. " | Setting to '" .. confirmation.newValue .. "' but got '" .. arguments.value .. "' server-side. Maybe key doesn't exist on the server?")
792 end
793 end
794 end
795end
796
797--! \brief Cleans up pending confirmations that have not been confirmed within a timeout period.
798--! \param timeout \number The timeout in seconds to clean up pending confirmations. Default: 300 seconds (5 minutes).
799function FrameworkZ.Foundation:CleanupConfirmations(timeout)
800 local now = getTimestamp()
801
802 for id, entry in pairs(self.PendingConfirmations) do
803 if now - entry.sentAt > timeout then
805 print(("[FZ] Cleaned up stale confirmation: %s"):format(tostring(id)))
806 end
807 end
808end
809
810--! TODO move to shared timer hook
811function FrameworkZ.Foundation:EveryDays()
812 self:CleanupConfirmations(60 * 5) -- 5 minutes
813end
814
815--[[ Hook System
816
817
818
819██ ██ ██████ ██████ ██ ██ ███████ ██ ██ ███████ ████████ ███████ ███ ███
820██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████
821███████ ██ ██ ██ ██ █████ ███████ ████ ███████ ██ █████ ██ ████ ██
822██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
823██ ██ ██████ ██████ ██ ██ ███████ ██ ███████ ██ ███████ ██ ██
824
826
827--]]
828
829--! \brief Categories for framework hooks. HOOK_CATEGORY_FRAMEWORK = "framework"
831
832--! \brief Categories for module hooks. HOOK_CATEGORY_MODULE = "module"
833HOOK_CATEGORY_MODULE = "module"
834
835--! \brief Categories for gamemode hooks. HOOK_CATEGORY_GAMEMODE = "gamemode"
836HOOK_CATEGORY_GAMEMODE = "gamemode"
837
838--! \brief Categories for plugin hooks. HOOK_CATEGORY_PLUGIN = "plugin"
839HOOK_CATEGORY_PLUGIN = "plugin"
841--! \brief Categories for generic hooks. HOOK_CATEGORY_GENERIC = "generic"
842HOOK_CATEGORY_GENERIC = "generic"
843
846 module = {},
847 gamemode = {},
848 plugin = {},
849 generic = {}
850}
852FrameworkZ.Foundation.RegisteredHooks = {
853 framework = {},
854 module = {},
855 gamemode = {},
856 plugin = {},
857 generic = {}
858}
860-- NOTE Last documented from here
861
862--! \brief Add a new hook handler to the list.
863--! \param hookName \string The name of the hook handler to add.
864--! \param category \string The category of the hook (framework, module, plugin, generic).
865function FrameworkZ.Foundation:AddHookHandler(hookName, category)
866 category = category or HOOK_CATEGORY_GENERIC
867 self.HookHandlers[category][hookName] = true
868end
869
870--! \brief Add a new hook handler to the list for all categories.
871--! \param hookName \string The name of the hook handler to add.
872function FrameworkZ.Foundation:AddAllHookHandlers(hookName)
873 self:AddHookHandler(hookName, HOOK_CATEGORY_FRAMEWORK)
874 self:AddHookHandler(hookName, HOOK_CATEGORY_MODULE)
875 self:AddHookHandler(hookName, HOOK_CATEGORY_GAMEMODE)
876 self:AddHookHandler(hookName, HOOK_CATEGORY_PLUGIN)
877 self:AddHookHandler(hookName, HOOK_CATEGORY_GENERIC)
878end
879
880--! \brief Remove a hook handler from the list.
881--! \param hookName \string The name of the hook handler to remove.
882--! \param category \string The category of the hook (framework, module, plugin, generic).
883function FrameworkZ.Foundation:RemoveHookHandler(hookName, category)
884 category = category or HOOK_CATEGORY_GENERIC
885 self.HookHandlers[category][hookName] = nil
886end
887
888--! \brief Register hook handlers for the framework.
889--! \param framework \table The framework table containing the functions.
890function FrameworkZ.Foundation:RegisterFrameworkHandler()
891 self:RegisterHandlers(self, HOOK_CATEGORY_FRAMEWORK)
892end
894--! \brief Unregister hook handlers for the framework.
895--! \param framework \table The framework table containing the functions.
896function FrameworkZ.Foundation:UnregisterFrameworkHandler()
897 self:UnregisterHandlers(self, HOOK_CATEGORY_FRAMEWORK)
898end
900--! \brief Register hook handlers for a module.
901--! \param module \table The module table containing the functions.
902function FrameworkZ.Foundation:RegisterModuleHandler(module)
903 self:RegisterHandlers(module, HOOK_CATEGORY_MODULE)
904end
906--! \brief Unregister hook handlers for a module.
907--! \param module \table The module table containing the functions.
908function FrameworkZ.Foundation:UnregisterModuleHandler(module)
909 self:UnregisterHandlers(module, HOOK_CATEGORY_MODULE)
910end
911
912--! \brief Register hook handlers for the gamemode.
913--! \param module \table The module table containing the functions.
914function FrameworkZ.Foundation:RegisterGamemodeHandler(gamemode)
915 self:RegisterHandlers(gamemode, HOOK_CATEGORY_GAMEMODE)
916end
917
918--! \brief Unregister hook handlers for the gamemode.
919--! \param module \table The module table containing the functions.
920function FrameworkZ.Foundation:UnregisterGamemodeHandler(gamemode)
921 self:UnregisterHandlers(gamemode, HOOK_CATEGORY_GAMEMODE)
922end
923
924--! \brief Register hook handlers for a plugin.
925--! \param plugin \table The plugin table containing the functions.
926function FrameworkZ.Foundation:RegisterPluginHandler(plugin)
927 self:RegisterHandlers(plugin, HOOK_CATEGORY_PLUGIN)
928end
929
930--! \brief Unregister hook handlers for a plugin.
931--! \param plugin \table The plugin table containing the functions.
932function FrameworkZ.Foundation:UnregisterPluginHandler(plugin)
933 self:UnregisterHandlers(plugin, HOOK_CATEGORY_PLUGIN)
934end
935
936--! \brief Register hook handlers for a plugin.
937--! \param plugin \table The plugin table containing the functions.
938function FrameworkZ.Foundation:RegisterGenericHandler()
939 self:RegisterHandlers(nil, HOOK_CATEGORY_GENERIC)
940end
941
942--! \brief Unregister hook handlers for a plugin.
943--! \param plugin \table The plugin table containing the functions.
944function FrameworkZ.Foundation:UnregisterGenericHandler()
945 self:UnregisterHandlers(nil, HOOK_CATEGORY_GENERIC)
946end
947
948--! \brief Register handlers for a specific category.
949--! \param object \table The object containing the functions.
950--! \param category \string The category of the hook (framework, module, plugin, generic).
951function FrameworkZ.Foundation:RegisterHandlers(objectOrHandlers, category)
952 category = category or HOOK_CATEGORY_GENERIC
953 if not self.HookHandlers[category] then
954 error("Invalid category: " .. tostring(category))
955 end
956
957 -- Iterate over the hook names using pairs since HookHandlers is now a dictionary
958 for hookName, _ in pairs(self.HookHandlers[category]) do
959 if objectOrHandlers and type(objectOrHandlers) == "table" then
960 -- Check if the object/table has a function for the hookName
961 local handlerFunction = objectOrHandlers[hookName]
962 if handlerFunction and type(handlerFunction) == "function" then
963 self:RegisterHandler(hookName, handlerFunction, objectOrHandlers, hookName, category)
964 end
965 else
966 -- objectOrHandlers is nil or not a table
967 -- Try to get the function from the global environment
968 local handler = _G[hookName]
969 if handler and type(handler) == "function" then
970 self:RegisterHandler(hookName, handler, nil, nil, category)
971 end
972 end
973 end
974end
975
976--! \brief Unregister handlers for a specific category.
977--! \param object \table The object containing the functions.
978--! \param category \string The category of the hook (framework, module, plugin, generic).
979function FrameworkZ.Foundation:UnregisterHandlers(objectOrHandlers, category)
980 category = category or HOOK_CATEGORY_GENERIC
981 if not self.HookHandlers[category] then
982 error("Invalid category: " .. tostring(category))
983 end
984
985 for hookName, _ in pairs(self.HookHandlers[category]) do
986 if objectOrHandlers and type(objectOrHandlers) == "table" then
987 local handlerFunction = objectOrHandlers[hookName]
988 if handlerFunction and type(handlerFunction) == "function" then
989 self:UnregisterHandler(hookName, handlerFunction, objectOrHandlers, hookName, category)
990 end
991 else
992 local handler = _G[hookName]
993 if handler and type(handler) == "function" then
994 self:UnregisterHandler(hookName, handler, nil, nil, category)
995 end
996 end
997 end
998end
999
1000--! \brief Register a handler for a hook.
1001--! \param hookName \string The name of the hook.
1002--! \param handler \function The function to call when the hook is executed.
1003--! \param object \table (Optional) The object containing the function.
1004--! \param functionName \string (Optional) The name of the function to call.
1005--! \param category \string The category of the hook (framework, module, plugin, generic).
1006function FrameworkZ.Foundation:RegisterHandler(hookName, handler, object, functionName, category)
1007 category = category or HOOK_CATEGORY_GENERIC
1008 self.RegisteredHooks[category][hookName] = self.RegisteredHooks[category][hookName] or {}
1009 --object.__skipWrap = true
1010
1011 if object and functionName then
1012 table.insert(self.RegisteredHooks[category][hookName], {
1013 handler = function(...)
1014 object[functionName](...)
1015 end,
1016 object = object,
1017 functionName = functionName
1018 })
1019 else
1020 table.insert(self.RegisteredHooks[category][hookName], {
1021 handler = handler,
1022 object = object
1023 })
1024 end
1025end
1026
1027--! \brief Unregister a handler from a hook.
1028--! \param hookName \string The name of the hook.
1029--! \param handler \function The function to unregister.
1030--! \param object \table (Optional) The object containing the function.
1031--! \param functionName \string (Optional) The name of the function to unregister.
1032--! \param category \string The category of the hook (framework, module, plugin, generic).
1033function FrameworkZ.Foundation:UnregisterHandler(hookName, handler, object, functionName, category)
1034 category = category or HOOK_CATEGORY_GENERIC
1035 local hooks = self.RegisteredHooks[category] and self.RegisteredHooks[category][hookName]
1036 if hooks then
1037 for i = #hooks, 1, -1 do
1038 if object and functionName then
1039 if hooks[i].object == object and hooks[i].functionName == functionName then
1040 table.remove(hooks, i)
1041 end
1042 else
1043 if hooks[i] == handler then
1044 table.remove(hooks, i)
1045 end
1046 end
1047 end
1048 end
1049end
1050
1051--! \brief Execute a given hook by its hook name for its given category.
1052--! \note When a function is defined and registered as a hook, sometimes it's as an object. However in the definition it could be as some.func() or some:func() (notice the period and colon between the examples). If the function is defined as some:func() then the object is passed as the first argument. If the function is defined as some.func() then the object is not passed as the first argument, in which case we would also need to define some.func_PassOverHookableObject function which must return \boolean true. This tells the hook system to not supply the object as the first argument if the function is apart of an object in the first place. Generic function hooks do not store an object and so do not have to worry about defining that additional property on its own function.
1053--! \param hookName \string The name of the hook.
1054--! \param category \string The category of the hook (framework, module, plugin, generic).
1055--! \param ... \multiple Additional arguments to pass to the hook functions.
1056function FrameworkZ.Foundation:ExecuteHook(hookName, category, ...)
1057 category = category or HOOK_CATEGORY_GENERIC
1058 local args = FrameworkZ.Utilities:Pack(...)
1059
1060 local hooks = self.RegisteredHooks[category] and self.RegisteredHooks[category][hookName]
1061 if hooks then
1062 for _, hook in ipairs(hooks) do
1063 local func = hook.handler
1064 local object = hook.object
1065 local functionName = hook.functionName
1066
1067 if func then
1068 if object and functionName then
1069 local shouldPassOverHookableObject = rawget(object, functionName .. "_PassOverHookableObject")
1070
1071 if shouldPassOverHookableObject then
1072 func(FrameworkZ.Utilities:Unpack(args))
1073 else
1074 if args[1] ~= object then
1075 func(object, FrameworkZ.Utilities:Unpack(args))
1076 else
1077 func(FrameworkZ.Utilities:Unpack(args))
1078 end
1079 end
1080 else
1081 func(FrameworkZ.Utilities:Unpack(args))
1082 end
1083 end
1084 end
1085 end
1086end
1087
1088--! \brief Execute all of the hooks.
1089--! \param hookName \string The name of the hook.
1090--! \param ... \vararg Additional arguments to pass to the hook functions.
1091function FrameworkZ.Foundation:ExecuteAllHooks(hookName, ...)
1092 for category, hooks in pairs(self.RegisteredHooks) do
1093 self:ExecuteHook(hookName, category, ...)
1094 end
1095end
1096
1097--! \brief Execute the framework hooks.
1098--! \param hookName \string The name of the hook.
1099--! \param ... \vararg Additional arguments to pass to the hook functions.
1100function FrameworkZ.Foundation:ExecuteFrameworkHooks(hookName, ...)
1101 self:ExecuteHook(hookName, HOOK_CATEGORY_FRAMEWORK, ...)
1102end
1103
1104--! \brief Execute module hooks.
1105--! \param hookName \string The name of the hook.
1106--! \param ... \vararg Additional arguments to pass to the hook functions.
1107function FrameworkZ.Foundation:ExecuteModuleHooks(hookName, ...)
1108 self:ExecuteHook(hookName, HOOK_CATEGORY_MODULE, ...)
1109end
1110
1111--! \brief Execute the gamemode hooks.
1112--! \param hookName \string The name of the hook.
1113--! \param ... \vararg Additional arguments to pass to the hook functions.
1114function FrameworkZ.Foundation:ExecuteGamemodeHooks(hookName, ...)
1115 self:ExecuteHook(hookName, HOOK_CATEGORY_GAMEMODE, ...)
1116end
1117
1118--! \brief Execute plugin hooks.
1119--! \param hookName \string The name of the hook.
1120--! \param ... \vararg Additional arguments to pass to the hook functions.
1121function FrameworkZ.Foundation:ExecutePluginHooks(hookName, ...)
1122 self:ExecuteHook(hookName, HOOK_CATEGORY_PLUGIN, ...)
1123end
1124
1125--! \brief Execute generic hooks.
1126--! \param hookName \string The name of the hook.
1127--! \param ... \vararg Additional arguments to pass to the hook functions.
1128function FrameworkZ.Foundation:ExecuteGenericHooks(hookName, ...)
1129 self:ExecuteHook(hookName, HOOK_CATEGORY_GENERIC, ...)
1130end
1131
1132--[[ Hooks
1133
1134
1135
1136██ ██ ██████ ██████ ██ ██ ███████
1137██ ██ ██ ██ ██ ██ ██ ██ ██
1138███████ ██ ██ ██ ██ █████ ███████
1139██ ██ ██ ██ ██ ██ ██ ██ ██
1140██ ██ ██████ ██████ ██ ██ ███████
1141
1142
1143
1144--]]
1145
1146function FrameworkZ.Foundation.Events:EveryDays()
1147 self:ExecuteAllHooks("EveryDays")
1148end
1149FrameworkZ.Foundation:AddAllHookHandlers("EveryDays")
1150
1151-- The LoadGridSquare event is not defined for hook usage because of performance reasons.
1152
1153function FrameworkZ.Foundation.Events:OnClientCommand(module, command, isoPlayer, arguments)
1154 self:ExecuteAllHooks("OnClientCommand", module, command, isoPlayer, arguments)
1155end
1156FrameworkZ.Foundation:AddAllHookHandlers("OnClientCommand")
1157
1158function FrameworkZ.Foundation.Events:OnConnected()
1159 self:ExecuteAllHooks("OnConnected")
1160end
1161FrameworkZ.Foundation:AddAllHookHandlers("OnConnected")
1162
1163function FrameworkZ.Foundation.Events:OnCreatePlayer()
1164 self:ExecuteAllHooks("OnCreatePlayer")
1165end
1166FrameworkZ.Foundation:AddAllHookHandlers("OnCreatePlayer")
1167
1168function FrameworkZ.Foundation.Events:OnDisconnect()
1169 self:ExecuteAllHooks("OnDisconnect")
1170end
1171FrameworkZ.Foundation:AddAllHookHandlers("OnDisconnect")
1172
1173function FrameworkZ.Foundation.Events:OnFillInventoryObjectContextMenu(player, context, items)
1174 self:ExecuteAllHooks("OnFillInventoryObjectContextMenu", player, context, items)
1175end
1176FrameworkZ.Foundation:AddAllHookHandlers("OnFillInventoryObjectContextMenu")
1177
1178function FrameworkZ.Foundation.Events:OnFillWorldObjectContextMenu(playerNumber, context, worldObjects, test)
1179 self:ExecuteAllHooks("OnFillWorldObjectContextMenu", playerNumber, context, worldObjects, test)
1180end
1181FrameworkZ.Foundation:AddAllHookHandlers("OnFillWorldObjectContextMenu")
1182
1183function FrameworkZ.Foundation.Events:OnGameStart()
1184 self:ExecuteAllHooks("OnGameStart")
1185end
1186FrameworkZ.Foundation:AddAllHookHandlers("OnGameStart")
1187
1188function FrameworkZ.Foundation.Events:OnInitGlobalModData(isNewGame)
1189 self:ExecuteAllHooks("OnInitGlobalModData", isNewGame)
1190end
1191FrameworkZ.Foundation:AddAllHookHandlers("OnInitGlobalModData")
1192
1193function FrameworkZ.Foundation.Events:OnKeyStartPressed(key)
1194 self:ExecuteAllHooks("OnKeyStartPressed", key)
1195end
1196FrameworkZ.Foundation:AddAllHookHandlers("OnKeyStartPressed")
1197
1198function FrameworkZ.Foundation.Events:OnMainMenuEnter()
1199 self:ExecuteAllHooks("OnMainMenuEnter")
1200end
1201FrameworkZ.Foundation:AddAllHookHandlers("OnMainMenuEnter")
1202
1203function FrameworkZ.Foundation.Events:OnObjectLeftMouseButtonDown(object, x, y)
1204 self:ExecuteAllHooks("OnObjectLeftMouseButtonDown", object, x, y)
1205end
1206FrameworkZ.Foundation:AddAllHookHandlers("OnObjectLeftMouseButtonDown")
1207
1208function FrameworkZ.Foundation.Events:OnPlayerDeath(player)
1209 self:ExecuteAllHooks("OnPlayerDeath", player)
1210end
1211FrameworkZ.Foundation:AddAllHookHandlers("OnPlayerDeath")
1212
1213function FrameworkZ.Foundation.Events:OnPreFillInventoryObjectContextMenu(playerID, context, items)
1214 self:ExecuteAllHooks("OnPreFillInventoryObjectContextMenu", playerID, context, items)
1215end
1216FrameworkZ.Foundation:AddAllHookHandlers("OnPreFillInventoryObjectContextMenu")
1217
1218function FrameworkZ.Foundation.Events:OnReceiveGlobalModData(key, data)
1219 self:ExecuteAllHooks("OnReceiveGlobalModData", key, data)
1220end
1221FrameworkZ.Foundation:AddAllHookHandlers("OnReceiveGlobalModData")
1222
1223function FrameworkZ.Foundation.Events:OnResetLua(reason)
1224 self:ExecuteAllHooks("OnResetLua", reason)
1225end
1226FrameworkZ.Foundation:AddAllHookHandlers("OnResetLua")
1227
1228function FrameworkZ.Foundation.Events:OnServerCommand(module, command, arguments)
1229 self:ExecuteAllHooks("OnServerCommand", module, command, arguments)
1230end
1231FrameworkZ.Foundation:AddAllHookHandlers("OnServerCommand")
1232
1233function FrameworkZ.Foundation.Events:OnServerStarted()
1234 self:ExecuteAllHooks("OnServerStarted")
1235end
1236FrameworkZ.Foundation:AddAllHookHandlers("OnServerStarted")
1237
1238-- The OnTick event is not defined for hook usage because of performance reasons.
1239
1240--[[ Hook Callbacks
1241
1242
1243
1244██ ██ ██████ ██████ ██ ██ ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████
1245██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1246███████ ██ ██ ██ ██ █████ ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████
1247██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1248██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████
1249
1250
1251
1252--]]
1253
1254local startTime
1255local serverSaveTick = 0
1256
1257function FrameworkZ.Foundation:ServerTick()
1258 if serverSaveTick >= FrameworkZ.Config.Options.TicksUntilServerSave then
1259 self:SaveData()
1260 print("[FZ] Server data saved...")
1261
1262 serverSaveTick = 0
1263 else
1264 serverSaveTick = serverSaveTick + 1
1265 end
1266end
1267
1268function FrameworkZ.Foundation:StartServerTick()
1269 if not isServer() then return end
1270
1271 local loops = 0
1272
1273 FrameworkZ.Timers:Create("FZ_SERVER_TICK", FrameworkZ.Config.Options.ServerTickInterval, 0, function()
1274 self:ExecuteAllHooks("ServerTick")
1275 end)
1276
1277 FrameworkZ.Timers:Create("FZ_SERVER_TIMER", 1, 0, function()
1278 self:ExecuteAllHooks("ServerTimer", loops)
1279
1280 loops = loops + 1
1281 end)
1282end
1283FrameworkZ.Foundation:AddAllHookHandlers("ServerTick")
1284FrameworkZ.Foundation:AddAllHookHandlers("ServerTimer")
1285
1286function FrameworkZ.Foundation:OnServerStarted()
1287 if isServer() then
1288 self:StartServerTick()
1289 end
1290end
1291
1292--! \brief Called when the game starts. Executes the OnGameStart function for all modules.
1293function FrameworkZ.Foundation:OnGameStart()
1294 if isClient() then
1295 self.Initialized = false
1296
1297 local isoPlayer = getPlayer()
1298 startTime = getTimestampMs()
1299
1300 self:ExecuteFrameworkHooks("PreInitializeClient", isoPlayer)
1301 end
1302end
1303
1304function FrameworkZ.Foundation:PreInitializeClient(isoPlayer)
1305 FrameworkZ.Interfaces:Initialize()
1306
1307 local sidebar = ISEquippedItem.instance
1308 self.fzuiTabMenu = FrameworkZ.UI.TabMenu:new(sidebar:getX(), sidebar:getY() + sidebar:getHeight() + 10, sidebar:getWidth(), 40, getPlayer())
1309 self.fzuiTabMenu:initialise()
1310 self.fzuiTabMenu:addToUIManager()
1311
1312 local ui = FrameworkZ.UI.Introduction:new(0, 0, getCore():getScreenWidth(), getCore():getScreenHeight(), getPlayer())
1313 ui:initialise()
1314 ui:addToUIManager()
1315
1316 self:ExecuteModuleHooks("PreInitializeClient", isoPlayer)
1317 self:ExecuteGamemodeHooks("PreInitializeClient",isoPlayer)
1318 self:ExecutePluginHooks("PreInitializeClient", isoPlayer)
1319
1320 self:ExecuteFrameworkHooks("InitializeClient", isoPlayer)
1321end
1322FrameworkZ.Foundation:AddAllHookHandlers("PreInitializeClient")
1323
1324function FrameworkZ.Foundation:InitializeClient(isoPlayer)
1325 FrameworkZ.Timers:Simple(FrameworkZ.Config.Options.InitializationDuration, function()
1326 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnInitializePlayer", function(data, serverSideInitialized, playerData, charactersData)
1327 if serverSideInitialized then
1328 local username = isoPlayer:getUsername()
1329
1330 if not VoiceManager:playerGetMute(username) then
1331 VoiceManager:playerSetMute(username)
1332 end
1333
1334 isoPlayer:clearWornItems()
1335 isoPlayer:getInventory():clear()
1336
1337 local gown = isoPlayer:getInventory():AddItem("Base.HospitalGown")
1338 isoPlayer:setWornItem(gown:getBodyLocation(), gown)
1339
1340 local slippers = isoPlayer:getInventory():AddItem("Base.Shoes_Slippers")
1341 local color = Color.new(1, 1, 1, 1);
1342 slippers:setColor(color);
1343 slippers:getVisual():setTint(ImmutableColor.new(color));
1344 slippers:setCustomColor(true);
1345 isoPlayer:setWornItem(slippers:getBodyLocation(), slippers)
1346
1347 isoPlayer:setGodMod(true)
1348 isoPlayer:setInvincible(true)
1349 isoPlayer:setHealth(1.0)
1350
1351 local bodyParts = isoPlayer:getBodyDamage():getBodyParts()
1352 for i=1, bodyParts:size() do
1353 local bP = bodyParts:get(i-1)
1354 bP:RestoreToFullHealth();
1355
1356 if bP:getStiffness() > 0 then
1357 bP:setStiffness(0)
1358 isoPlayer:getFitness():removeStiffnessValue(BodyPartType.ToString(bP:getType()))
1359 end
1360 end
1361
1362 isoPlayer:setInvisible(true)
1363 isoPlayer:setGhostMode(true)
1364 isoPlayer:setNoClip(true)
1365
1366 isoPlayer:setX(FrameworkZ.Config.Options.LimboX)
1367 isoPlayer:setY(FrameworkZ.Config.Options.LimboY)
1368 isoPlayer:setZ(FrameworkZ.Config.Options.LimboZ)
1369 isoPlayer:setLx(FrameworkZ.Config.Options.LimboX)
1370 isoPlayer:setLy(FrameworkZ.Config.Options.LimboY)
1371 isoPlayer:setLz(FrameworkZ.Config.Options.LimboZ)
1372
1373 self:InitializePlayer(isoPlayer, playerData, charactersData)
1374 end
1375 end)
1376 end)
1377end
1378FrameworkZ.Foundation:AddAllHookHandlers("InitializeClient")
1379
1380if isServer() then
1381 function FrameworkZ.Foundation.OnInitializePlayer(data)
1382 return FrameworkZ.Foundation:InitializePlayer(data.isoPlayer)
1383 end
1384end
1385
1386function FrameworkZ.Foundation:RestorePlayer(isoPlayer, player, username, playerData, charactersData)
1387 if not player then return end
1388 if isClient() and (not playerData or not charactersData) then return end
1389
1390 if isServer() then
1391 playerData = self:GetData(isoPlayer, "Players", username)
1392 charactersData = self:GetData(isoPlayer, "Characters", username)
1393 end
1394
1395 if playerData then
1396 player:RestoreData(playerData)
1397
1398 if charactersData then
1399 player:SetCharacters(charactersData)
1400
1401 return playerData, charactersData
1402 elseif isServer() then
1403 local characters = player:GetCharacters()
1405 end
1406
1407 return playerData, false
1408 elseif isServer() then
1409 local saveableData = player:GetSaveableData()
1410 local characters = player:GetCharacters()
1411
1412 self:SetData(isoPlayer, "Players", username, saveableData)
1414 end
1415
1416 return false, false
1417end
1418
1419function FrameworkZ.Foundation:InitializePlayer(isoPlayer, playerData, charactersData)
1420 if not isoPlayer then return false, nil, nil end
1421
1422 local player = FrameworkZ.Players:Initialize(isoPlayer) if not player then return false end
1423 local username = player:GetUsername()
1424 local options = FrameworkZ.Config.Options
1425 local x, y, z = options.LimboX, options.LimboY, options.LimboZ
1426
1427 isoPlayer:setX(x)
1428 isoPlayer:setY(y)
1429 isoPlayer:setZ(z)
1430 isoPlayer:setLx(x)
1431 isoPlayer:setLy(y)
1432 isoPlayer:setLz(z)
1433
1434 if isServer() then
1435 playerData, charactersData = self:RestorePlayer(isoPlayer, player, username)
1436 elseif isClient() then
1437 playerData, charactersData = self:RestorePlayer(isoPlayer, player, username, playerData, charactersData)
1438 end
1439
1440 if playerData then
1441 print("[FZ] Restored player for '" .. username .. "'.")
1442 else
1443 print("[FZ] Created new player for '" .. username .. "'.")
1444 end
1445
1446 if charactersData then
1447 local charactersRestored = ""
1448
1449 for k, character in pairs(charactersData) do
1450 charactersRestored = "#" .. k .. " " .. charactersRestored .. character.INFO_NAME .. ", "
1451 end
1452
1453 charactersRestored = string.sub(charactersRestored, 1, -3) -- Remove the last comma and space
1454
1455 print("[FZ] Restored characters for '" .. username .. "': " .. (charactersRestored == "" and "[N/A]" or charactersRestored))
1456 else
1457 print("[FZ] Created new characters field for '" .. username .. "'.")
1458 end
1459
1460 self:ExecuteModuleHooks("InitializeClient", isoPlayer)
1461 self:ExecuteGamemodeHooks("InitializeClient", isoPlayer)
1462 self:ExecutePluginHooks("InitializeClient", isoPlayer)
1463
1464 self:ExecuteFrameworkHooks("PostInitializeClient", player)
1465
1466 return true, playerData, charactersData
1467end
1468
1469function FrameworkZ.Foundation:PostInitializeClient(player)
1470 self:ExecuteModuleHooks("PostInitializeClient", player)
1471 self:ExecuteGamemodeHooks("PostInitializeClient", player)
1472 self:ExecutePluginHooks("PostInitializeClient", player)
1473
1474 if isClient() then
1475 FrameworkZ.Foundation.InitializationNotification = FrameworkZ.Notifications:AddToQueue("Initialized in " .. tostring(string.format(" %.2f", (getTimestampMs() - startTime - FrameworkZ.Config:GetOption("InitializationDuration") * 1000) / 1000)) .. " seconds.", FrameworkZ.Notifications.Types.Success, nil, FrameworkZ.UI.Introduction.instance)
1476 end
1477
1478 self.Initialized = true
1479end
1480FrameworkZ.Foundation:AddAllHookHandlers("PostInitializeClient")
1481
1482function FrameworkZ.Foundation.OnTeleportToLimbo(data)
1483 local isoPlayer = data.isoPlayer
1484
1485 if not isoPlayer then print("[FZ] ERROR: Failed to teleport player to limbo, isoPlayer is nil.") return false end
1486 if not FrameworkZ.Foundation:TeleportToLimbo(isoPlayer) then print("[FZ] ERROR: Failed to teleport player to limbo.") return false end
1487
1488 return true
1489end
1490
1491function FrameworkZ.Foundation:TeleportToLimbo(isoPlayer)
1492 if not isoPlayer then return false end
1493
1494 local x, y, z = FrameworkZ.Config:GetOption("LimboX"), FrameworkZ.Config:GetOption("LimboY"), FrameworkZ.Config:GetOption("LimboZ")
1495
1496 isoPlayer:setX(x)
1497 isoPlayer:setY(y)
1498 isoPlayer:setZ(z)
1499 isoPlayer:setLx(x)
1500 isoPlayer:setLy(y)
1501 isoPlayer:setLz(z)
1502
1503 return true
1504end
1505
1506FrameworkZ.Foundation:AddAllHookHandlers("PlayerTick")
1507
1508
1509--[[ Data Storage
1510
1511
1512
1513██████ █████ ████████ █████ ███████ ████████ ██████ ██████ █████ ██████ ███████
1514██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1515██ ██ ███████ ██ ███████ ███████ ██ ██ ██ ██████ ███████ ██ ███ █████
1516██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1517██████ ██ ██ ██ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ███████
1518
1519
1520
1521--]]
1522
1523-- STORAGE BACKEND
1524FrameworkZ.Foundation.StorageName = "FZ_STORAGE"
1525FrameworkZ.Foundation.Namespaces = FrameworkZ.Foundation.Namespaces or {}
1526FrameworkZ.Foundation.SyncQueues = FrameworkZ.Foundation.SyncQueues or {}
1527
1528--! \brief Registers a storage namespace, e.g., "Players"
1529--! \param name \string The name of the namespace to register.
1530--! \note This must be used in the shared scope within an OnInitGlobalModData function.
1531function FrameworkZ.Foundation:RegisterNamespace(name)
1532 if isServer() then
1533 self.Namespaces[name] = ModData.getOrCreate(self.StorageName .. "_" .. name)
1534 end
1535
1536 if isClient() then
1537 self.Namespaces[name] = self.Namespaces[name] or {}
1538 end
1539end
1540
1541function FrameworkZ.Foundation:GetLocalData(namespace, keys)
1542 local ns = self:GetNamespace(namespace)
1543
1544 if ns then
1545 if not keys then
1546 return ns or "FZ ERROR CODE: 1"
1547 elseif type(keys) == "string" then
1548 return ns[keys] or "FZ ERROR CODE: 1"
1549 elseif type(keys) == "table" then
1550 return self:GetNestedValue(ns, keys) or "FZ ERROR CODE: 1"
1551 end
1552 end
1553
1554 print("[FZ] ERROR: Failed to get value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1555
1556 return "FZ ERROR CODE: 1"
1557end
1558
1559function FrameworkZ.Foundation:SetLocalData(namespace, keys, value)
1560 local ns = self:GetNamespace(namespace)
1561
1562 if ns then
1563 if not keys then
1564 self.Namespaces[namespace] = value
1565 elseif type(keys) == "string" then
1566 self.Namespaces[namespace][keys] = value
1567 elseif type(keys) == "table" then
1568 value = self:SetNestedValue(ns, keys, value)
1569 end
1570
1571 return true
1572 end
1573
1574 print("[FZ] ERROR: Failed to set value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1575
1576 return false
1577end
1578
1579if isClient() then
1580
1581 function FrameworkZ.Foundation.OnSaveData(data)
1582 FrameworkZ.Foundation:SaveData(data.isoPlayer)
1583 end
1584
1585 function FrameworkZ.Foundation.OnSaveNamespace(data)
1586 FrameworkZ.Foundation:SaveNamespace(data.isoPlayer, data.namespace)
1587 end
1588elseif isServer() then
1589
1590 function FrameworkZ.Foundation.OnSaveData(data)
1591 FrameworkZ.Foundation:SaveData(data.isoPlayer)
1592 end
1593
1594 function FrameworkZ.Foundation.OnSaveNamespace(data, namespace)
1595 FrameworkZ.Foundation:SaveNamespace(data.isoPlayer, namespace)
1596 end
1597end
1598
1599function FrameworkZ.Foundation.OnGetData(data, namespace, keys, subscriptionID)
1600 if isServer() then
1601 local value = FrameworkZ.Foundation:GetLocalData(namespace, keys)
1602
1603 if value ~= "FZ ERROR CODE: 1" and subscriptionID then
1604 FrameworkZ.Foundation:Fire(subscriptionID, data, FrameworkZ.Utilities:Pack(namespace, keys, value))
1605 end
1606
1607 return value
1608 end
1609end
1610
1611function FrameworkZ.Foundation.OnSetData(data, namespace, keys, value, subscriptionID, broadcast)
1612 if isServer() then
1613 if not FrameworkZ.Foundation:SetLocalData(namespace, keys, value) then
1614 return false
1615 end
1616
1617 if subscriptionID then
1618 FrameworkZ.Foundation:Fire(subscriptionID, data, FrameworkZ.Utilities:Pack(namespace, keys, value))
1619 end
1620
1621 -- Broadcast is handled here because the value should be managed before broadcasting [instead of in FrameworkZ.Foundation:Set()].
1622 if broadcast then
1623 FrameworkZ.Foundation:Broadcast(namespace, keys, value)
1624 end
1625
1626 return true
1627 end
1628end
1629FrameworkZ.Foundation:AddAllHookHandlers("OnStorageGet")
1630FrameworkZ.Foundation:AddAllHookHandlers("OnStorageSet")
1631
1632--! \brief Gets a value from a namespace by key(s).
1633--! \param isoPlayer \object (Optional) The player to get the value for. This is only used on the client to send a request to the server.
1634--! \param namespace \string The namespace to get the value from.
1635--! \param keys \string or \table The key(s) to get the value for. Supplying a table will do a lookup through all keys and get value at the last index.
1636--! \param subscriptionID \string (Optional) A unique identifier for the subscription to be fired server-side after the value has been retrieved.
1637--! \param callback \function (Optional) A callback function to call after the value is retrieved. This is only used on the client to handle the response from the server.
1638--! \return \any (except \function) The value for the key in the namespace, or false if the namespace or key does not exist. Server-side only.
1639--! \note If called on the client, the value may only be accessed in the callback immediately, or later after data has synchronized.
1640function FrameworkZ.Foundation:GetData(isoPlayer, namespace, keys, subscriptionID, callback)
1641 if isClient() then
1642 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnGetData", function(data, value)
1643 if value == "FZ ERROR CODE: 1" then
1644 print("[FZ] ERROR: Failed to get server-side value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1645 return
1646 end
1647
1648 self:SetLocalData(namespace, keys, value)
1649
1650 if callback then
1651 callback(isoPlayer, namespace, keys, value)
1652 end
1653 end, namespace, keys, subscriptionID)
1654 elseif isServer() then
1655 local value = self:GetLocalData(namespace, keys)
1656
1657 if value == "FZ ERROR CODE: 1" then
1658 print("[FZ] ERROR: Failed to get server-side value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1659 return false
1660 end
1661
1662 if callback then
1663 callback(isoPlayer, namespace, keys, value)
1664 end
1665
1666 return value
1667 end
1668end
1669
1670--! \brief Sets a value in a namespace and (optionally) broadcasts to all clients.
1671--! \param isoPlayer \object (Optional when called server-side only) The player to set the value for. This is only used on the client to send a request to the server.
1672--! \param namespace \string The namespace to set the value in.
1673--! \param keys \string or \table The key(s) to set the value for. Supplying a table will do a lookup through all keys and set value at the last index.
1674--! \param value \any (except \function) The value to set.
1675--! \param subscriptionID \string (Optional) A unique identifier for the subscription to be fired server-side after the value has been set.
1676--! \param broadcast \boolean (Optional) Whether or not to broadcast the value to all clients.
1677--! \param callback \function (Optional) A callback function to call after the value is set. This is only used on the client to handle the response from the server.
1678--! \return \boolean Whether or not the value was set successfully. Server-side only.
1679--! \note If called on the client, the value may only be accessed in the callback immediately, or later after data has synchronized.
1680function FrameworkZ.Foundation:SetData(isoPlayer, namespace, keys, value, subscriptionID, broadcast, callback)
1681 if isClient() then
1682 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnSetData", function(data, success)
1683 if not success then
1684 print("[FZ] ERROR: Failed to set server-side value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1685 return
1686 end
1687
1688 self:SetLocalData(namespace, keys, value)
1689
1690 if callback then
1691 callback(isoPlayer, namespace, keys, value)
1692 end
1693 end, namespace, keys, value, subscriptionID, broadcast)
1694 elseif isServer() then
1695 local success = self:SetLocalData(namespace, keys, value)
1696
1697 if not success then
1698 print("[FZ] ERROR: Failed to set server-side value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1699 return false
1700 end
1701
1702 if callback then
1703 callback(isoPlayer, namespace, keys, value)
1704 end
1705
1706 return success
1707 end
1708end
1709
1710function FrameworkZ.Foundation:RestoreData(isoPlayer, command, namespace, keys, callback)
1711 if isServer() then
1712 local stored = self:GetLocalData(namespace, keys)
1713
1714 if stored and type(stored) == "table" then
1715 --[[for k, v in pairs(stored) do
1716 object[k] = v
1717 end--]]
1718
1719 if callback then
1720 callback(true, stored)
1721 end
1722
1723 return true
1724 end
1725
1726 if callback then
1727 callback(false)
1728 end
1729
1730 return false
1731 elseif isClient() then
1732 local handlerName = "RestoreData_" .. tostring(namespace) .. "_" .. tostring(command)
1733
1734 local function tempHook(_isoPlayer, _command, _namespace, _keys, value)
1735 if _namespace == namespace and _command == command then
1736 if value then
1737 if callback then callback(true, value) end
1738 else
1739 if callback then callback(false) end
1740 end
1741
1742 -- Unregister this handler after first use
1743 FrameworkZ.Foundation:UnregisterHandler("OnStorageGet", tempHook, nil, handlerName, HOOK_CATEGORY_FRAMEWORK)
1744 end
1745 end
1746
1747 FrameworkZ.Foundation:RegisterHandler("OnStorageGet", tempHook, nil, handlerName, HOOK_CATEGORY_FRAMEWORK)
1748 self:GetData(isoPlayer, command, namespace, keys)
1749 end
1750end
1751
1752function FrameworkZ.Foundation:SaveData(isoPlayer)
1753 if isClient() then
1754 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnSaveData", nil)
1755 elseif isServer() then
1756 for namespace, data in pairs(self.Namespaces) do
1757 ModData.add(self.StorageName .. "_" .. namespace, data)
1758 end
1759 end
1760end
1761
1762function FrameworkZ.Foundation:SaveNamespace(isoPlayer, namespace)
1763 if isClient() then
1764 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnSaveNamespace", nil, namespace)
1765 elseif isServer() then
1766 local data = self.Namespaces[namespace]
1767
1768 if data then
1769 ModData.add(self.StorageName .. "_" .. namespace, data)
1770 end
1771 end
1772end
1773
1774--! \brief Removes a key from a namespace and broadcasts removal
1775function FrameworkZ.Foundation:RemoveData(namespace, key)
1776 if isServer() then
1777 local ns = self.Namespaces[namespace]
1778 if ns then
1779 ns[key] = nil
1780 self:Broadcast(namespace, key, true)
1781 end
1782 end
1783end
1784
1785--! \brief Retrieves the entire namespace table
1786function FrameworkZ.Foundation:GetNamespace(namespace)
1787 return self.Namespaces[namespace]
1788end
1789
1790--! \brief Sends a specific key to a specific player
1791function FrameworkZ.Foundation:SyncToPlayer(isoPlayer, namespace, key)
1792 self:SendFire(isoPlayer, "FrameworkZ.Storage.OnSync", function(data, success)
1793 if success and data and data.namespace and data.key and data.value then
1794 self.Namespaces[data.namespace] = self.Namespaces[data.namespace] or {}
1795 self.Namespaces[data.namespace][data.key] = data.value
1796 end
1797 end, {
1798 namespace = namespace,
1799 key = key,
1800 isoPlayer = isoPlayer
1801 })
1802end
1803
1804--! \brief Broadcasts updated or removed data to all clients
1805function FrameworkZ.Foundation:Broadcast(namespace, key, remove)
1806 local value = self:Get(namespace, key)
1807
1808 self:SendFire(nil, remove and "FrameworkZ.Storage.OnRemove" or "FrameworkZ.Storage.OnSyncBroadcast", {
1809 namespace = namespace,
1810 key = key,
1811 value = value
1812 })
1813end
1814
1815--! \brief Server-side response to client sync request
1816function FrameworkZ.Foundation.OnSync(data)
1817 local namespace, key = data.namespace, data.key
1818 if not namespace or not key then return false end
1819 local value = FrameworkZ.Foundation:Get(namespace, key)
1820 if not value then return false end
1821 return { namespace = namespace, key = key, value = value }
1822end
1823--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnSync", FrameworkZ.Foundation.OnSync)
1824
1825--! \brief Client receives sync data from broadcast
1826function FrameworkZ.Foundation.OnSyncBroadcast(data)
1827 if not data.namespace or not data.key then return end
1828 FrameworkZ.Foundation.Namespaces[data.namespace] = FrameworkZ.Foundation.Namespaces[data.namespace] or {}
1829 FrameworkZ.Foundation.Namespaces[data.namespace][data.key] = data.value
1830end
1831--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnSyncBroadcast", FrameworkZ.Foundation.OnSyncBroadcast)
1832
1833--! \brief Client receives key removal broadcast
1834function FrameworkZ.Foundation.OnRemoveData(data)
1835 if not data.namespace or not data.key then return end
1836 local ns = FrameworkZ.Foundation.Namespaces[data.namespace]
1837 if ns then ns[data.key] = nil end
1838end
1839--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnRemoveData", FrameworkZ.Foundation.OnRemove)
1840
1841--! \brief Queue a key in a namespace for sync
1842function FrameworkZ.Foundation:QueueBatchSync(isoPlayer, namespace, key)
1843 if not isoPlayer then return end
1844 local username = isoPlayer:getUsername()
1845 self.SyncQueues[username] = self.SyncQueues[username] or {}
1846 table.insert(self.SyncQueues[username], { namespace = namespace, key = key })
1847end
1848
1849--! \brief Clear the sync queue for a player
1850function FrameworkZ.Foundation:ClearBatchSyncQueue(isoPlayer)
1851 if isoPlayer and isoPlayer:getUsername() then
1852 self.SyncQueues[isoPlayer:getUsername()] = nil
1853 end
1854end
1855
1856--! \brief Begin processing the queued keys for a player
1857function FrameworkZ.Foundation:StartBatchSync(isoPlayer, interval, onComplete)
1858 if not isoPlayer then return end
1859 local username = isoPlayer:getUsername()
1860 local queue = self.SyncQueues[username]
1861 if not queue or #queue == 0 then return end
1862
1863 local tickRate = interval or 0.1
1864 local timerName = "BatchSync_" .. username
1865
1866 local index = 1
1867 local total = #queue
1868 local calledPre = {}
1869 local calledPost = {}
1870 local namespaceCounts = {}
1871 local namespaceProgress = {}
1872
1873 for _, item in ipairs(queue) do
1874 local ns = item.namespace
1875 namespaceCounts[ns] = (namespaceCounts[ns] or 0) + 1
1876 end
1877
1878 self:ExecuteAllHooks("PreBatchSync", isoPlayer, nil, nil)
1879 FrameworkZ.Timers:Remove(timerName)
1880
1881 FrameworkZ.Timers:Create(timerName, tickRate, total, function()
1882 local item = queue[index]
1883 if item then
1884 local ns, key = item.namespace, item.key
1885
1886 if not calledPre[ns] then
1887 self:ExecuteAllHooks("PreBatchSync", isoPlayer, ns, nil)
1888 calledPre[ns] = true
1889 end
1890
1891 self:ExecuteAllHooks("OnBatchSync", isoPlayer, ns, key)
1892 self:SyncToPlayer(isoPlayer, ns, key)
1893
1894 namespaceProgress[ns] = (namespaceProgress[ns] or 0) + 1
1895
1896 if namespaceProgress[ns] == namespaceCounts[ns] then
1897 self:ExecuteAllHooks("PostBatchSync", isoPlayer, ns, key)
1898 calledPost[ns] = true
1899 end
1900 end
1901
1902 index = index + 1
1903
1904 if index > total then
1905 if type(onComplete) == "function" then
1906 onComplete(isoPlayer)
1907 end
1908
1909 self:ExecuteAllHooks("PostBatchSync", isoPlayer, nil, nil)
1910 end
1911 end)
1912
1913 self.SyncQueues[username] = nil
1914end
1915
1916function FrameworkZ.Foundation:ProcessSaveableData(object, ignoreList, encodeList)
1917 local saveableData = {}
1918
1919 for k, v in pairs(object) do
1920 if FrameworkZ.Utilities:TableContainsValue(ignoreList, k) then
1921 -- skip ignored keys
1922 elseif type(v) == "function" then
1923 -- skip functions
1924 elseif type(v) == "table" then
1925 if FrameworkZ.Utilities:TableContainsValue(encodeList, k) and v.GetSaveableData then
1926 saveableData[k] = v:GetSaveableData()
1927
1928 if not saveableData[k] then
1929 print("[FZ] Failed to save '" .. tostring(v) .. "' at '" .. tostring(k) .. "'. OBJECT:GetSaveableData() is not implemented.")
1930 end
1931 else
1932 -- Recursively process plain tables
1933 saveableData[k] = self:ProcessSaveableData(v, ignoreList, encodeList)
1934 end
1935 else
1936 saveableData[k] = v
1937 end
1938 end
1939
1940 return saveableData
1941end
1942
1943--[[ Finalization
1944
1945
1946
1947███████ ██ ███ ██ █████ ██ ██ ███████ █████ ████████ ██ ██████ ███ ██
1948██ ██ ████ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ████ ██
1949█████ ██ ██ ██ ██ ███████ ██ ██ ███ ███████ ██ ██ ██ ██ ██ ██ ██
1950██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██
1951██ ██ ██ ████ ██ ██ ███████ ██ ███████ ██ ██ ██ ██ ██████ ██ ████
1952
1953
1954
1955-]]
1956
1957--! \brief Initializes the framework foundation.
1958--! \note The LoadGridSquare event is not added to the hook system for performance reasons, as it is called very frequently.
1959function FrameworkZ.Foundation:Initialize()
1960 local events = {}
1961
1962 for k, v in pairs(Events) do
1963 if v and v.Add then
1964 local oldEventAdd = v.Add
1965
1966 if not events[k] then
1967 events[k] = {}
1968 end
1969
1970 events[k].Add = function(func)
1971 local function wrappedFunc(...)
1972 func(self, ...)
1973 end
1974
1975 oldEventAdd(wrappedFunc)
1976 end
1977 end
1978 end
1979
1980 events.EveryDays.Add(self.Events.EveryDays)
1981 events.OnClientCommand.Add(self.Events.OnClientCommand)
1982 events.OnConnected.Add(self.Events.OnConnected)
1983 events.OnCreatePlayer.Add(self.Events.OnCreatePlayer)
1984 events.OnDisconnect.Add(self.Events.OnDisconnect)
1985 events.OnFillInventoryObjectContextMenu.Add(self.Events.OnFillInventoryObjectContextMenu)
1986 events.OnFillWorldObjectContextMenu.Add(self.Events.OnFillWorldObjectContextMenu)
1987 events.OnGameStart.Add(self.Events.OnGameStart)
1988 events.OnInitGlobalModData.Add(self.Events.OnInitGlobalModData)
1989 events.OnKeyStartPressed.Add(self.Events.OnKeyStartPressed)
1990 events.OnMainMenuEnter.Add(self.Events.OnMainMenuEnter)
1991 events.OnObjectLeftMouseButtonDown.Add(self.Events.OnObjectLeftMouseButtonDown)
1992 events.OnPlayerDeath.Add(self.Events.OnPlayerDeath)
1993 events.OnPreFillInventoryObjectContextMenu.Add(self.Events.OnPreFillInventoryObjectContextMenu)
1994 events.OnReceiveGlobalModData.Add(self.Events.OnReceiveGlobalModData)
1995 events.OnServerCommand.Add(self.Events.OnServerCommand)
1996 events.OnServerStarted.Add(self.Events.OnServerStarted)
1997
1998 if isServer() then
1999 --self:Subscribe("FrameworkZ.Foundation.OnInitializeClient", self.OnInitializeClient)
2000 self:Subscribe("FrameworkZ.Foundation.OnGetData", self.OnGetData)
2001 self:Subscribe("FrameworkZ.Foundation.OnInitializePlayer", self.OnInitializePlayer)
2002 self:Subscribe("FrameworkZ.Foundation.OnSetData", self.OnSetData)
2003 self:Subscribe("FrameworkZ.Foundation.OnSaveData", self.OnSaveData)
2004 self:Subscribe("FrameworkZ.Foundation.OnSaveNamespace", self.OnSaveNamespace)
2005 self:Subscribe("FrameworkZ.Foundation.OnTeleportToLimbo", self.OnTeleportToLimbo)
2006 end
2007end
2008
2009FrameworkZ.Foundation:RegisterFramework()
void local y()
void local x()
void player Characters()
void context()
void FrameworkZ UI Introduction()
void id()
void local success
void items()
void item()
void category()
void type()
void self FrameworkZ UI self nil
Definition MainMenu.lua:95
void self self
Definition MainMenu.lua:89
void local player()
void processingNotification backgroundColor a()
void local options()
void self username()
void local getSpecificPlayer()
void local name()
void if type v()
void subscriptionID()
void clientSentAt()
void local isClient()
void HOOK_CATEGORY_FRAMEWORK()
Categories for framework hooks. HOOK_CATEGORY_FRAMEWORK = "framework".
void subLastFiredAt()
void local Events()
void local callbacks()
void local bP()
void local isServer()
void local serverSaveTick()
void current()
void local channel()
void HOOK_CATEGORY_GAMEMODE()
Categories for gamemode hooks. HOOK_CATEGORY_GAMEMODE = "gamemode".
void broadcast()
void subCreatedAt()
void callbackID()
void HOOK_CATEGORY_PLUGIN()
Categories for plugin hooks. HOOK_CATEGORY_PLUGIN = "plugin".
void local data()
void createdAt()
void gamemode()
void module()
void HOOK_CATEGORY_MODULE()
Categories for module hooks. HOOK_CATEGORY_MODULE = "module".
void playerID()
void local getPlayer()
void HOOK_CATEGORY_GENERIC()
Categories for generic hooks. HOOK_CATEGORY_GENERIC = "generic".
void FrameworkZ Foundation()
void serverSentAt()
void if objectOrHandlers and type objectOrHandlers()
void meta lastFiredAt()
void subID()
void local gown()
void FrameworkZ Foundation SubscribersMeta()
Meta data for the subscribers. This is used to track when a channel was created and when it was last ...
void local payload()
void local color()
void results()
void key()
void local unpack()
void generic()
void elseif command()
void local onlineUsers()
void plugin()
void local callback()
void local slippers()
void isoPlayer()
void local subscribers()
void local handlerFunction()
void local characters()
void local charactersRestored()
void local ModData()
void local meta()
void framework()
void local requestID()
void value()
void if hooks()
void local newValue()
void FrameworkZ Foundation PendingConfirmations()
Pending confirmations for network requests. This is used to track requests that are waiting for a res...
void local returnValues()
void FrameworkZ Foundation NetworksName()
The name of the networking module for commands in OnClientCommand and OnServerCommand....
void local saveableData()
void local confirmation()
void args()
void local handler()
void FrameworkZ Foundation Subscribers()
Subscribers for the network system. This is used to track subscribers for channels.
void FrameworkZ Foundation HookHandlers()
void local bodyParts()
void sentAt()
void for i()
void FrameworkZ()
Foundation for FrameworkZ.
FrameworkZ Foundation Events
void ConnectToServer OnConnected()