5██████ ██████ ██████ ██ ██ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██
6██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
7██ ██ ██ ██ ██ ██ ██ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██
8██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
9██████ ██████ ██████ ██████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████
15--! \mainpage Main Page
16--! Created By RJ_RayJay
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.
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:
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.
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.
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.
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.
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/
50--! \page global_variables Global Variables
51--! \section FrameworkZ 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.
69--! Nil is a special value that represents the absence of a value. Nil is used to indicate that a variable has no value.
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.
85███████ ███████ ████████ ██ ██ ██████
87███████ █████ ██ ██ ██ ██████
89███████ ███████ ██ ██████ ██
96local getPlayer = getPlayer
97local isClient = isClient
98local isServer = isServer
99local ModData = ModData
102FrameworkZ = FrameworkZ or {}
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
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")
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
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
144 return self.Modules[moduleName]
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
155 return module.MetaObject
158--! \brief Register FrameworkZ. This is called after framework definition.
159function FrameworkZ.Foundation:RegisterFramework()
160 FrameworkZ.Foundation:RegisterFrameworkHandler()
161 FrameworkZ:RegisterObject(self)
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)
171--! \brief Get the version of FrameworkZ Foundation.
172--! \return \string The version of the FrameworkZ Foundation.
173function FrameworkZ.Foundation:GetVersion()
177FrameworkZ.Foundation = FrameworkZ.Foundation.New()
183███ ██ ███████ ████████ ██ ██ ██████ ██████ ██ ██ ██ ███ ██ ██████
184████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
185██ ██ ██ █████ ██ ██ █ ██ ██ ██ ██████ █████ ██ ██ ██ ██ ██ ███
186██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
187██ ████ ███████ ██ ███ ███ ██████ ██ ██ ██ ██ ██ ██ ████ ██████
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"
196--! \brief Pending confirmations for network requests. This is used to track requests that are waiting for a response.
197FrameworkZ.Foundation.PendingConfirmations = {}
199--! \brief Subscribers for the network system. This is used to track subscribers for channels.
200FrameworkZ.Foundation.Subscribers = {}
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 = {}
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))
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
219 return table.concat(path, ".")
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.
225 local stringKey =
self:PathToString(
key)
227 self.Subscribers[stringKey] = {}
228 self.SubscribersMeta[stringKey] = {
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.
248 local stringKey =
self:PathToString(
key)
257 local stringKey =
self:PathToString(
key)
262--! \brief Check if
a channel exists for
a key. This will return true if the
channel exists, false otherwise.
264--! \return \
boolean True if the
channel exists, false otherwise.
266 local stringKey =
self:PathToString(
key)
271--! \brief Log all channels and their
subscribers to the console. This is useful for debugging and understanding the network system.
280 print("Channel:",
key, "(created at " .. createdString .. ", last fired at " .. firedString .. ")")
282 for
id, _ in pairs(subs) do
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.
296 if type(idOrCallback) ==
"function" then
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"
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.
337 for k2, v2 in ipairs(
v)
do
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.
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.
361 if not
self:HasChannel(
key) then
362 print("[FZ] Warning: Received fire event for unknown ID: ",
key)
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.
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.
417 sendClientCommand(
self.NetworksName,
"GetData", {
419 broadcast = broadcast,
420 callbackID = callbackID,
421 requestID = requestID,
422 args = FrameworkZ.Utilities:Pack(...)
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.
445 sendClientCommand(
self.NetworksName,
"SetData", {
448 broadcast = broadcast,
449 callbackID = callbackID,
450 requestID = requestID
456--! \brief Sends
a fire event to the server or client. This is used to send events to
subscribers.
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.
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"]
507 for _,
key in ipairs(path) do
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
524 for
i = 1,
#path - 1 do
537 --! \brief Handles incoming commands from the client on the server.
539 --! \param
command \
string The
command that was sent by the client.
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.
551 if arguments.callbackID then
566 if not arguments.broadcast then
567 sendServerCommand(
isoPlayer,
self.NetworksName,
"ReturnData", {
571 requestID = arguments.requestID,
572 returnValues = returnValues,
573 args = arguments.args
585 args = arguments.args
589 elseif
command ==
"SetData" then
590 local
key = arguments.key
591 local
value = arguments.value
608 if arguments.callbackID then
637 elseif
command ==
"SendFire" then
638 local
subID = arguments.subID
656 elseif
command ==
"ConfirmFire" then
660 local
meta = arguments.meta or {}
684 --! \brief Handles incoming commands from the server on the client.
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.
718 elseif
command ==
"ReturnData" then
743 elseif
command ==
"SendFire" then
763 elseif
command ==
"ConfirmFire" then
767 local
meta = arguments.meta or {}
785 elseif
command ==
"FailedSet" 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?")
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).
800 local now = getTimestamp()
803 if now - entry.
sentAt > timeout then
805 print((
"[FZ] Cleaned up stale confirmation: %s"):format(tostring(
id)))
810--! TODO move to shared timer hook
812 self:CleanupConfirmations(60 * 5) -- 5 minutes
819██ ██ ██████ ██████ ██ ██ ███████ ██ ██ ███████ ████████ ███████ ███ ███
820██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████
821███████ ██ ██ ██ ██ █████ ███████ ████ ███████ ██ █████ ██ ████ ██
822██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
823██ ██ ██████ ██████ ██ ██ ███████ ██ ███████ ██ ███████ ██ ██
852FrameworkZ.Foundation.RegisteredHooks = {
860-- NOTE Last documented from here
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
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)
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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))
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)
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)
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))
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)
992 local handler = _G[hookName]
993 if handler and type(handler) == "function" then
994 self:UnregisterHandler(hookName, handler, nil, nil, category)
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
1011 if object and functionName then
1012 table.insert(self.RegisteredHooks[category][hookName], {
1013 handler = function(...)
1014 object[functionName](...)
1017 functionName = functionName
1020 table.insert(self.RegisteredHooks[category][hookName], {
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]
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)
1043 if hooks[i] == handler then
1044 table.remove(hooks, i)
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(...)
1060 local hooks = self.RegisteredHooks[category] and self.RegisteredHooks[category][hookName]
1062 for _, hook in ipairs(hooks) do
1063 local func = hook.handler
1064 local object = hook.object
1065 local functionName = hook.functionName
1068 if object and functionName then
1069 local shouldPassOverHookableObject = rawget(object, functionName .. "_PassOverHookableObject")
1071 if shouldPassOverHookableObject then
1072 func(FrameworkZ.Utilities:Unpack(args))
1074 if args[1] ~= object then
1075 func(object, FrameworkZ.Utilities:Unpack(args))
1077 func(FrameworkZ.Utilities:Unpack(args))
1081 func(FrameworkZ.Utilities:Unpack(args))
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, ...)
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, ...)
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, ...)
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, ...)
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, ...)
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, ...)
1136██ ██ ██████ ██████ ██ ██ ███████
1137██ ██ ██ ██ ██ ██ ██ ██ ██
1138███████ ██ ██ ██ ██ █████ ███████
1139██ ██ ██ ██ ██ ██ ██ ██ ██
1140██ ██ ██████ ██████ ██ ██ ███████
1146function FrameworkZ.Foundation.Events:EveryDays()
1147 self:ExecuteAllHooks("EveryDays")
1149FrameworkZ.Foundation:AddAllHookHandlers("EveryDays")
1151-- The LoadGridSquare event is not defined for hook usage because of performance reasons.
1153function FrameworkZ.Foundation.Events:OnClientCommand(module, command, isoPlayer, arguments)
1154 self:ExecuteAllHooks("OnClientCommand", module, command, isoPlayer, arguments)
1156FrameworkZ.Foundation:AddAllHookHandlers("OnClientCommand")
1158function FrameworkZ.Foundation.Events:OnConnected()
1159 self:ExecuteAllHooks("OnConnected")
1161FrameworkZ.Foundation:AddAllHookHandlers("OnConnected")
1163function FrameworkZ.Foundation.Events:OnCreatePlayer()
1164 self:ExecuteAllHooks("OnCreatePlayer")
1166FrameworkZ.Foundation:AddAllHookHandlers("OnCreatePlayer")
1168function FrameworkZ.Foundation.Events:OnDisconnect()
1169 self:ExecuteAllHooks("OnDisconnect")
1171FrameworkZ.Foundation:AddAllHookHandlers("OnDisconnect")
1173function FrameworkZ.Foundation.Events:OnFillInventoryObjectContextMenu(player, context, items)
1174 self:ExecuteAllHooks("OnFillInventoryObjectContextMenu", player, context, items)
1176FrameworkZ.Foundation:AddAllHookHandlers("OnFillInventoryObjectContextMenu")
1178function FrameworkZ.Foundation.Events:OnFillWorldObjectContextMenu(playerNumber, context, worldObjects, test)
1179 self:ExecuteAllHooks("OnFillWorldObjectContextMenu", playerNumber, context, worldObjects, test)
1181FrameworkZ.Foundation:AddAllHookHandlers("OnFillWorldObjectContextMenu")
1183function FrameworkZ.Foundation.Events:OnGameStart()
1184 self:ExecuteAllHooks("OnGameStart")
1186FrameworkZ.Foundation:AddAllHookHandlers("OnGameStart")
1188function FrameworkZ.Foundation.Events:OnInitGlobalModData(isNewGame)
1189 self:ExecuteAllHooks("OnInitGlobalModData", isNewGame)
1191FrameworkZ.Foundation:AddAllHookHandlers("OnInitGlobalModData")
1193function FrameworkZ.Foundation.Events:OnKeyStartPressed(key)
1194 self:ExecuteAllHooks("OnKeyStartPressed", key)
1196FrameworkZ.Foundation:AddAllHookHandlers("OnKeyStartPressed")
1198function FrameworkZ.Foundation.Events:OnMainMenuEnter()
1199 self:ExecuteAllHooks("OnMainMenuEnter")
1201FrameworkZ.Foundation:AddAllHookHandlers("OnMainMenuEnter")
1203function FrameworkZ.Foundation.Events:OnObjectLeftMouseButtonDown(object, x, y)
1204 self:ExecuteAllHooks("OnObjectLeftMouseButtonDown", object, x, y)
1206FrameworkZ.Foundation:AddAllHookHandlers("OnObjectLeftMouseButtonDown")
1208function FrameworkZ.Foundation.Events:OnPlayerDeath(player)
1209 self:ExecuteAllHooks("OnPlayerDeath", player)
1211FrameworkZ.Foundation:AddAllHookHandlers("OnPlayerDeath")
1213function FrameworkZ.Foundation.Events:OnPreFillInventoryObjectContextMenu(playerID, context, items)
1214 self:ExecuteAllHooks("OnPreFillInventoryObjectContextMenu", playerID, context, items)
1216FrameworkZ.Foundation:AddAllHookHandlers("OnPreFillInventoryObjectContextMenu")
1218function FrameworkZ.Foundation.Events:OnReceiveGlobalModData(key, data)
1219 self:ExecuteAllHooks("OnReceiveGlobalModData", key, data)
1221FrameworkZ.Foundation:AddAllHookHandlers("OnReceiveGlobalModData")
1223function FrameworkZ.Foundation.Events:OnResetLua(reason)
1224 self:ExecuteAllHooks("OnResetLua", reason)
1226FrameworkZ.Foundation:AddAllHookHandlers("OnResetLua")
1228function FrameworkZ.Foundation.Events:OnServerCommand(module, command, arguments)
1229 self:ExecuteAllHooks("OnServerCommand", module, command, arguments)
1231FrameworkZ.Foundation:AddAllHookHandlers("OnServerCommand")
1233function FrameworkZ.Foundation.Events:OnServerStarted()
1234 self:ExecuteAllHooks("OnServerStarted")
1236FrameworkZ.Foundation:AddAllHookHandlers("OnServerStarted")
1238-- The OnTick event is not defined for hook usage because of performance reasons.
1244██ ██ ██████ ██████ ██ ██ ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████
1245██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1246███████ ██ ██ ██ ██ █████ ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████
1247██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1248██ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████
1255local serverSaveTick = 0
1257function FrameworkZ.Foundation:ServerTick()
1258 if serverSaveTick >= FrameworkZ.Config.Options.TicksUntilServerSave then
1260 print("[FZ] Server data saved...")
1264 serverSaveTick = serverSaveTick + 1
1268function FrameworkZ.Foundation:StartServerTick()
1269 if not isServer() then return end
1273 FrameworkZ.Timers:Create("FZ_SERVER_TICK", FrameworkZ.Config.Options.ServerTickInterval, 0, function()
1274 self:ExecuteAllHooks("ServerTick")
1277 FrameworkZ.Timers:Create("FZ_SERVER_TIMER", 1, 0, function()
1278 self:ExecuteAllHooks("ServerTimer", loops)
1283FrameworkZ.Foundation:AddAllHookHandlers("ServerTick")
1284FrameworkZ.Foundation:AddAllHookHandlers("ServerTimer")
1286function FrameworkZ.Foundation:OnServerStarted()
1288 self:StartServerTick()
1292--! \brief Called when the game starts. Executes the OnGameStart function for all modules.
1293function FrameworkZ.Foundation:OnGameStart()
1295 self.Initialized = false
1297 local isoPlayer = getPlayer()
1298 startTime = getTimestampMs()
1300 self:ExecuteFrameworkHooks("PreInitializeClient", isoPlayer)
1304function FrameworkZ.Foundation:PreInitializeClient(isoPlayer)
1305 FrameworkZ.Interfaces:Initialize()
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()
1312 local ui = FrameworkZ.UI.Introduction:new(0, 0, getCore():getScreenWidth(), getCore():getScreenHeight(), getPlayer())
1316 self:ExecuteModuleHooks("PreInitializeClient", isoPlayer)
1317 self:ExecuteGamemodeHooks("PreInitializeClient",isoPlayer)
1318 self:ExecutePluginHooks("PreInitializeClient", isoPlayer)
1320 self:ExecuteFrameworkHooks("InitializeClient", isoPlayer)
1322FrameworkZ.Foundation:AddAllHookHandlers("PreInitializeClient")
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()
1330 if not VoiceManager:playerGetMute(username) then
1331 VoiceManager:playerSetMute(username)
1334 isoPlayer:clearWornItems()
1335 isoPlayer:getInventory():clear()
1337 local gown = isoPlayer:getInventory():AddItem("Base.HospitalGown")
1338 isoPlayer:setWornItem(gown:getBodyLocation(), gown)
1340 local slippers = isoPlayer:getInventory():AddItem("Base.Shoes_Slippers")
1341 local color = Color.new(1, 1, 1, 1);
1354 bP:RestoreToFullHealth();
1356 if bP:getStiffness() > 0 then
1358 isoPlayer:getFitness():removeStiffnessValue(BodyPartType.ToString(
bP:getType()))
1373 self:InitializePlayer(
isoPlayer, playerData, charactersData)
1387 if not
player then return end
1388 if
isClient() and (not playerData or not charactersData) then return end
1396 player:RestoreData(playerData)
1398 if charactersData then
1399 player:SetCharacters(charactersData)
1401 return playerData, charactersData
1407 return playerData, false
1446 if charactersData then
1449 for k, character in pairs(charactersData) do
1450 charactersRestored = "
#" .. k .. " " .. charactersRestored .. character.INFO_NAME .. ", "
1453 charactersRestored = string.sub(charactersRestored, 1, -3) -- Remove the last comma and space
1455 print("[FZ] Restored characters for '" .. username .. "': " .. (charactersRestored == "" and "[N/A]" or charactersRestored))
1457 print("[FZ] Created new characters field for '" .. username .. "'.")
1460 self:ExecuteModuleHooks("InitializeClient", isoPlayer)
1461 self:ExecuteGamemodeHooks("InitializeClient", isoPlayer)
1462 self:ExecutePluginHooks("InitializeClient", isoPlayer)
1464 self:ExecuteFrameworkHooks("PostInitializeClient", player)
1466 return true, playerData, charactersData
1469function FrameworkZ.Foundation:PostInitializeClient(player)
1470 self:ExecuteModuleHooks("PostInitializeClient", player)
1471 self:ExecuteGamemodeHooks("PostInitializeClient", player)
1472 self:ExecutePluginHooks("PostInitializeClient", player)
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)
1478 self.Initialized = true
1480FrameworkZ.Foundation:AddAllHookHandlers("PostInitializeClient")
1482function FrameworkZ.Foundation.OnTeleportToLimbo(data)
1483 local isoPlayer = data.isoPlayer
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
1491function FrameworkZ.Foundation:TeleportToLimbo(isoPlayer)
1492 if not isoPlayer then return false end
1494 local x, y, z = FrameworkZ.Config:GetOption("LimboX"), FrameworkZ.Config:GetOption("LimboY"), FrameworkZ.Config:GetOption("LimboZ")
1506FrameworkZ.Foundation:AddAllHookHandlers("PlayerTick")
1513██████ █████ ████████ █████ ███████ ████████ ██████ ██████ █████ ██████ ███████
1514██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1515██ ██ ███████ ██ ███████ ███████ ██ ██ ██ ██████ ███████ ██ ███ █████
1516██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1517██████ ██ ██ ██ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ███████
1524FrameworkZ.Foundation.StorageName = "FZ_STORAGE"
1525FrameworkZ.Foundation.Namespaces = FrameworkZ.Foundation.Namespaces or {}
1526FrameworkZ.Foundation.SyncQueues = FrameworkZ.Foundation.SyncQueues or {}
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)
1533 self.Namespaces[name] = ModData.getOrCreate(self.StorageName .. "_" .. name)
1537 self.Namespaces[name] = self.Namespaces[name] or {}
1541function FrameworkZ.Foundation:GetLocalData(namespace, keys)
1542 local ns = self:GetNamespace(namespace)
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"
1554 print("[FZ] ERROR: Failed to get value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1556 return "FZ ERROR CODE: 1"
1559function FrameworkZ.Foundation:SetLocalData(namespace, keys, value)
1560 local ns = self:GetNamespace(namespace)
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)
1574 print("[FZ] ERROR: Failed to set value for namespace '" .. (namespace and tostring(namespace) or "null") .. "' and key(s) '" .. FrameworkZ.Utilities:DumpTable(keys) .. "'")
1581 function FrameworkZ.Foundation.OnSaveData(data)
1582 FrameworkZ.Foundation:SaveData(data.isoPlayer)
1585 function FrameworkZ.Foundation.OnSaveNamespace(data)
1586 FrameworkZ.Foundation:SaveNamespace(data.isoPlayer, data.namespace)
1588elseif isServer() then
1590 function FrameworkZ.Foundation.OnSaveData(data)
1591 FrameworkZ.Foundation:SaveData(data.isoPlayer)
1594 function FrameworkZ.Foundation.OnSaveNamespace(data, namespace)
1595 FrameworkZ.Foundation:SaveNamespace(data.isoPlayer, namespace)
1599function FrameworkZ.Foundation.OnGetData(data, namespace, keys, subscriptionID)
1601 local value = FrameworkZ.Foundation:GetLocalData(namespace, keys)
1603 if value ~= "FZ ERROR CODE: 1" and subscriptionID then
1604 FrameworkZ.Foundation:Fire(subscriptionID, data, FrameworkZ.Utilities:Pack(namespace, keys, value))
1611function FrameworkZ.Foundation.OnSetData(data, namespace, keys, value, subscriptionID, broadcast)
1613 if not FrameworkZ.Foundation:SetLocalData(namespace, keys, value) then
1617 if subscriptionID then
1618 FrameworkZ.Foundation:Fire(subscriptionID, data, FrameworkZ.Utilities:Pack(namespace, keys, value))
1621 -- Broadcast is handled here because the value should be managed before broadcasting [instead of in FrameworkZ.Foundation:Set()].
1623 FrameworkZ.Foundation:Broadcast(namespace, keys, value)
1629FrameworkZ.Foundation:AddAllHookHandlers("OnStorageGet")
1630FrameworkZ.Foundation:AddAllHookHandlers("OnStorageSet")
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)
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) .. "'")
1648 self:SetLocalData(namespace, keys, value)
1651 callback(isoPlayer, namespace, keys, value)
1653 end, namespace, keys, subscriptionID)
1654 elseif isServer() then
1655 local value = self:GetLocalData(namespace, keys)
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) .. "'")
1663 callback(isoPlayer, namespace, keys, value)
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)
1682 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnSetData", function(data, success)
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) .. "'")
1688 self:SetLocalData(namespace, keys, value)
1691 callback(isoPlayer, namespace, keys, value)
1693 end, namespace, keys, value, subscriptionID, broadcast)
1694 elseif isServer() then
1695 local success = self:SetLocalData(namespace, keys, value)
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) .. "'")
1703 callback(isoPlayer, namespace, keys, value)
1710function FrameworkZ.Foundation:RestoreData(isoPlayer, command, namespace, keys, callback)
1712 local stored = self:GetLocalData(namespace, keys)
1714 if stored and type(stored) == "table" then
1715 --[[for k, v in pairs(stored) do
1720 callback(true, stored)
1731 elseif isClient() then
1732 local handlerName = "RestoreData_" .. tostring(namespace) .. "_" .. tostring(command)
1734 local function tempHook(_isoPlayer, _command, _namespace, _keys, value)
1735 if _namespace == namespace and _command == command then
1737 if callback then callback(true, value) end
1739 if callback then callback(false) end
1742 -- Unregister this handler after first use
1743 FrameworkZ.Foundation:UnregisterHandler("OnStorageGet", tempHook, nil, handlerName, HOOK_CATEGORY_FRAMEWORK)
1747 FrameworkZ.Foundation:RegisterHandler("OnStorageGet", tempHook, nil, handlerName, HOOK_CATEGORY_FRAMEWORK)
1748 self:GetData(isoPlayer, command, namespace, keys)
1752function FrameworkZ.Foundation:SaveData(isoPlayer)
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)
1762function FrameworkZ.Foundation:SaveNamespace(isoPlayer, namespace)
1764 self:SendFire(isoPlayer, "FrameworkZ.Foundation.OnSaveNamespace", nil, namespace)
1765 elseif isServer() then
1766 local data = self.Namespaces[namespace]
1769 ModData.add(self.StorageName .. "_" .. namespace, data)
1774--! \brief Removes a key from a namespace and broadcasts removal
1775function FrameworkZ.Foundation:RemoveData(namespace, key)
1777 local ns = self.Namespaces[namespace]
1780 self:Broadcast(namespace, key, true)
1785--! \brief Retrieves the entire namespace table
1786function FrameworkZ.Foundation:GetNamespace(namespace)
1787 return self.Namespaces[namespace]
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
1798 namespace = namespace,
1800 isoPlayer = isoPlayer
1804--! \brief Broadcasts updated or removed data to all clients
1805function FrameworkZ.Foundation:Broadcast(namespace, key, remove)
1806 local value = self:Get(namespace, key)
1808 self:SendFire(nil, remove and "FrameworkZ.Storage.OnRemove" or "FrameworkZ.Storage.OnSyncBroadcast", {
1809 namespace = namespace,
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 }
1823--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnSync", FrameworkZ.Foundation.OnSync)
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
1831--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnSyncBroadcast", FrameworkZ.Foundation.OnSyncBroadcast)
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
1839--FrameworkZ.Foundation:Subscribe("FrameworkZ.Storage.OnRemoveData", FrameworkZ.Foundation.OnRemove)
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 })
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
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
1863 local tickRate = interval or 0.1
1864 local timerName = "BatchSync_" .. username
1867 local total = #queue
1868 local calledPre = {}
1869 local calledPost = {}
1870 local namespaceCounts = {}
1871 local namespaceProgress = {}
1873 for _, item in ipairs(queue) do
1874 local ns = item.namespace
1875 namespaceCounts[ns] = (namespaceCounts[ns] or 0) + 1
1878 self:ExecuteAllHooks("PreBatchSync", isoPlayer, nil, nil)
1879 FrameworkZ.Timers:Remove(timerName)
1881 FrameworkZ.Timers:Create(timerName, tickRate, total, function()
1882 local item = queue[index]
1884 local ns, key = item.namespace, item.key
1886 if not calledPre[ns] then
1887 self:ExecuteAllHooks("PreBatchSync", isoPlayer, ns, nil)
1888 calledPre[ns] = true
1891 self:ExecuteAllHooks("OnBatchSync", isoPlayer, ns, key)
1892 self:SyncToPlayer(isoPlayer, ns, key)
1894 namespaceProgress[ns] = (namespaceProgress[ns] or 0) + 1
1896 if namespaceProgress[ns] == namespaceCounts[ns] then
1897 self:ExecuteAllHooks("PostBatchSync", isoPlayer, ns, key)
1898 calledPost[ns] = true
1904 if index > total then
1905 if type(onComplete) == "function" then
1906 onComplete(isoPlayer)
1909 self:ExecuteAllHooks("PostBatchSync", isoPlayer, nil, nil)
1913 self.SyncQueues[username] = nil
1916function FrameworkZ.Foundation:ProcessSaveableData(object, ignoreList, encodeList)
1917 local saveableData = {}
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
1924 elseif type(v) == "table" then
1925 if FrameworkZ.Utilities:TableContainsValue(encodeList, k) and v.GetSaveableData then
1926 saveableData[k] = v:GetSaveableData()
1928 if not saveableData[k] then
1929 print("[FZ] Failed to save '" .. tostring(v) .. "' at '" .. tostring(k) .. "'. OBJECT:GetSaveableData() is not implemented.")
1932 -- Recursively process plain tables
1933 saveableData[k] = self:ProcessSaveableData(v, ignoreList, encodeList)
1947███████ ██ ███ ██ █████ ██ ██ ███████ █████ ████████ ██ ██████ ███ ██
1948██ ██ ████ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ████ ██
1949█████ ██ ██ ██ ██ ███████ ██ ██ ███ ███████ ██ ██ ██ ██ ██ ██ ██
1950██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██
1951██ ██ ██ ████ ██ ██ ███████ ██ ███████ ██ ██ ██ ██ ██████ ██ ████
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()
1962 for k, v in pairs(Events) do
1964 local oldEventAdd = v.Add
1966 if not events[k] then
1970 events[k].Add = function(func)
1971 local function wrappedFunc(...)
1975 oldEventAdd(wrappedFunc)
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)
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)
2009FrameworkZ.Foundation:RegisterFramework()
void FrameworkZ UI Introduction()
void self FrameworkZ UI self nil
void processingNotification backgroundColor a()
void HOOK_CATEGORY_FRAMEWORK()
Categories for framework hooks. HOOK_CATEGORY_FRAMEWORK = "framework".
void local serverSaveTick()
void HOOK_CATEGORY_GAMEMODE()
Categories for gamemode hooks. HOOK_CATEGORY_GAMEMODE = "gamemode".
void HOOK_CATEGORY_PLUGIN()
Categories for plugin hooks. HOOK_CATEGORY_PLUGIN = "plugin".
void HOOK_CATEGORY_MODULE()
Categories for module hooks. HOOK_CATEGORY_MODULE = "module".
void HOOK_CATEGORY_GENERIC()
Categories for generic hooks. HOOK_CATEGORY_GENERIC = "generic".
void FrameworkZ Foundation()
void if objectOrHandlers and type objectOrHandlers()
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 handlerFunction()
void local charactersRestored()
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 FrameworkZ Foundation Subscribers()
Subscribers for the network system. This is used to track subscribers for channels.
void FrameworkZ Foundation HookHandlers()
Foundation for FrameworkZ.
FrameworkZ Foundation Events
void ConnectToServer OnConnected()