@expcluster/legacy 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/browser/package.json +40 -0
  3. package/dist/browser/tsconfig.browser.tsbuildinfo +1 -0
  4. package/dist/browser/web/index.d.ts +1 -0
  5. package/dist/browser/web/index.d.ts.map +1 -0
  6. package/dist/browser/web/index.js +2 -0
  7. package/dist/browser/web/index.js.map +1 -0
  8. package/dist/node/index.d.ts +3 -0
  9. package/dist/node/index.d.ts.map +1 -0
  10. package/dist/node/index.js +10 -0
  11. package/dist/node/index.js.map +1 -0
  12. package/dist/node/instance.d.ts +4 -0
  13. package/dist/node/instance.d.ts.map +1 -0
  14. package/dist/node/instance.js +8 -0
  15. package/dist/node/instance.js.map +1 -0
  16. package/dist/node/tsconfig.node.tsbuildinfo +1 -0
  17. package/dist/web/manifest.json +7 -0
  18. package/dist/web/static/exp_legacy.9b6538559ed5e689539c.js +355 -0
  19. package/dist/web/static/index_ts.8f9d451c8d3f7f293da6.js +22 -0
  20. package/dist/web/static/main.94e1b48804112ca8bdfc.js +118 -0
  21. package/dist/web/static/package_json.a83729efdb0697b12396.js +22 -0
  22. package/dist/web/static/web_index_tsx.e5a01ac0e4b0a6f6517f.js +21 -0
  23. package/index.ts +8 -0
  24. package/instance.ts +7 -0
  25. package/module/config/_file_loader.lua +29 -0
  26. package/module/config/advanced_start.lua +112 -0
  27. package/module/config/afk_kick.lua +14 -0
  28. package/module/config/bonus.lua +105 -0
  29. package/module/config/chat_reply.lua +136 -0
  30. package/module/config/compilatron.lua +25 -0
  31. package/module/config/death_logger.lua +15 -0
  32. package/module/config/deconlog.lua +11 -0
  33. package/module/config/discord_alerts.lua +25 -0
  34. package/module/config/expcore/permission_groups.lua +143 -0
  35. package/module/config/graftorio.lua +7 -0
  36. package/module/config/gui/autofill.lua +129 -0
  37. package/module/config/gui/player_list_actions.lua +201 -0
  38. package/module/config/gui/rockets.lua +47 -0
  39. package/module/config/gui/science.lua +21 -0
  40. package/module/config/gui/tasks.lua +13 -0
  41. package/module/config/gui/warps.lua +52 -0
  42. package/module/config/inventory_clear.lua +9 -0
  43. package/module/config/join_messages.lua +8 -0
  44. package/module/config/lawnmower.lua +6 -0
  45. package/module/config/logging.lua +30 -0
  46. package/module/config/miner.lua +8 -0
  47. package/module/config/module.lua +86 -0
  48. package/module/config/nukeprotect.lua +30 -0
  49. package/module/config/pollution_grading.lua +9 -0
  50. package/module/config/popup_messages.lua +10 -0
  51. package/module/config/preset_player_colours.lua +33 -0
  52. package/module/config/preset_player_quickbar.lua +6 -0
  53. package/module/config/protection.lua +17 -0
  54. package/module/config/repair.lua +8 -0
  55. package/module/config/research.lua +179 -0
  56. package/module/config/scorched_earth.lua +116 -0
  57. package/module/config/spawn_area.lua +255 -0
  58. package/module/config/station_auto_name.lua +11 -0
  59. package/module/config/statistics.lua +46 -0
  60. package/module/config/vlayer.lua +180 -0
  61. package/module/config/warnings.lua +38 -0
  62. package/module/control.lua +46 -0
  63. package/module/expcore/datastore.lua +884 -0
  64. package/module/expcore/external.lua +146 -0
  65. package/module/expcore/permission_groups.lua +355 -0
  66. package/module/expcore/player_data.lua +152 -0
  67. package/module/locale/en/addons.cfg +31 -0
  68. package/module/locale/en/data.cfg +114 -0
  69. package/module/locale/en/expcore.cfg +16 -0
  70. package/module/locale/en/gui.cfg +114 -0
  71. package/module/locale/zh-CN/addons.cfg +31 -0
  72. package/module/locale/zh-CN/data.cfg +114 -0
  73. package/module/locale/zh-CN/expcore.cfg +16 -0
  74. package/module/locale/zh-CN/gui.cfg +122 -0
  75. package/module/locale/zh-TW/addons.cfg +31 -0
  76. package/module/locale/zh-TW/data.cfg +114 -0
  77. package/module/locale/zh-TW/expcore.cfg +16 -0
  78. package/module/locale/zh-TW/gui.cfg +122 -0
  79. package/module/module.json +14 -0
  80. package/module/module_exports.lua +3 -0
  81. package/module/modules/control/jail.lua +119 -0
  82. package/module/modules/control/protection.lua +204 -0
  83. package/module/modules/control/reports.lua +225 -0
  84. package/module/modules/control/spectate.lua +173 -0
  85. package/module/modules/control/vlayer.lua +807 -0
  86. package/module/modules/control/warnings.lua +331 -0
  87. package/module/modules/control/warps.lua +488 -0
  88. package/module/modules/data/alt-view.lua +26 -0
  89. package/module/modules/data/greetings.lua +45 -0
  90. package/module/modules/data/language.lua +21 -0
  91. package/module/modules/data/player-colours.lua +63 -0
  92. package/module/modules/data/quickbar.lua +72 -0
  93. package/module/modules/data/statistics.lua +181 -0
  94. package/module/modules/data/tag.lua +82 -0
  95. package/module/modules/data/toolbar.lua +31 -0
  96. package/module/modules/graftorio/forcestats.lua +196 -0
  97. package/module/modules/graftorio/general.lua +63 -0
  98. package/module/modules/graftorio/require.lua +28 -0
  99. package/module/modules/graftorio/statics.lua +36 -0
  100. package/module/modules/gui/vlayer.lua +547 -0
  101. package/module/modules/gui/warp-list.lua +909 -0
  102. package/module/utils/event.lua +86 -0
  103. package/package.json +38 -0
  104. package/tsconfig.browser.json +4 -0
  105. package/tsconfig.json +7 -0
  106. package/tsconfig.node.json +5 -0
  107. package/web/index.tsx +0 -0
  108. package/webpack.config.js +29 -0
@@ -0,0 +1,884 @@
1
+ --[[-- Core Module - Datastore
2
+ - A module used to store data in the global table with the option to have it sync to an external source.
3
+ @core Datastore
4
+ @alias DatastoreManager
5
+
6
+ @usage-- Types of Datastore
7
+ -- This datastore will not save data externally and can be used to watch for updates on values within it
8
+ -- A common use might be to store data for a gui and only update the gui when a value changes
9
+ local LocalDatastore = Datastore.connect('LocalDatastore')
10
+
11
+ -- This datastore will allow you to use the save and request method, this allows you to have persistent data
12
+ -- Should be used over auto save as it creates less save requests, but this means you need to tell the data to be saved
13
+ -- We use this type for player data as we know the data only needs to be saved when the player leaves
14
+ local PersistentDatastore = Datastore.connect('PersistentDatastore', true) -- save_to_disk
15
+
16
+ -- This datastore is the same as above but the save method will be called automatically when ever you change a value
17
+ -- An auto save datastore should be used if the data does not change often, this can be global settings and things of that sort
18
+ -- If it is at all possible to setup events to unload and/or save the data then this is preferable
19
+ local AutosaveDatastore = Datastore.connect('AutosaveDatastore', true, true) -- save_to_disk, auto_save
20
+
21
+ -- Finally you can have a datastore that propagates its changes to all other connected servers, this means request does not need to be used
22
+ -- This should be used when you might have data conflicts while saving, this is done by pushing the saved value to all active servers
23
+ -- The request method has little use after server start as any external changes to the value will be pushed automatically
24
+ -- Auto save can also be used with this type and you should follow the same guidelines above for when this should be avoided
25
+ local PropagateDatastore = Datastore.connect('PropagateDatastore', true, false, true) -- save_to_disk, propagate_changes
26
+
27
+ @usage-- Using Datastores Locally
28
+ -- Once you have your datastore connection setup, any further requests with connect will return the same datastore
29
+ -- This is important to know because the settings passed as parameters you have an effect when it is first created
30
+
31
+ -- One useful thing that you might want to set up before runtime is a serializer, this will convert non string keys into strings
32
+ -- This serializer will allow use to pass a player object and still have it serialized to the players name
33
+ local ExampleData = Datastore.connect('ExampleData')
34
+ ExampleData:set_serializer(function(rawKey)
35
+ return rawKey.name
36
+ end)
37
+
38
+ -- If we want to get data from the datastore we can use get or get_all
39
+ local value = ExampleData:get(player, defaultValue)
40
+ local values = ExampleData:get_all()
41
+
42
+ -- If we want to set data then we can use set, increment, update, or update_all
43
+ ExampleData:set(player, 10)
44
+ ExampleData:increment(player)
45
+ ExampleData:update(player, function(player_name, value)
46
+ return value * 2
47
+ end)
48
+ ExampleData:update_all(function(player_name, value)
49
+ return value * 2
50
+ end)
51
+
52
+ -- If we want to remove data then we use remove
53
+ ExampleData:remove(player)
54
+
55
+ -- We can also listen for updates to a value done by any of the above methods with on_update
56
+ ExampleData:on_update(function(player_name, value)
57
+ game.print(player_name..' has had their example data updated to '..tostring(value))
58
+ end)
59
+
60
+ @usage-- Using Datastore Externally
61
+ -- If save_to_disk is used then this opens up the option for persistent data which you can request, save, and remove
62
+ -- All of the local methods are still usable put now there is the option for extra events
63
+ -- In order for this to work there must be an external script to read datastore.pipe and inject with Datastore.ingest
64
+
65
+ -- To request data you would use request and the on_load event, this event can be used to modify data before it is used
66
+ ExampleData:request(player)
67
+ ExampleData:on_load(function(player_name, value)
68
+ game.print('Loaded example data for '..player_name)
69
+ -- A value can be returned here to overwrite the received value
70
+ end)
71
+
72
+ -- To save data you would use save and the on_save event, this event can be used to modify data before it is saved
73
+ ExampleData:save(player)
74
+ ExampleData:on_save(function(player_name, value)
75
+ game.print('Saved example data for '..player_name)
76
+ -- A value can be returned here to overwrite the value which is saved
77
+ end)
78
+
79
+ -- To remove data locally but not externally, like if a player logs off, you would use unload and on_unload
80
+ ExampleData:unload(player)
81
+ ExampleData:on_unload(function(player_name, value)
82
+ game.print('Unloaded example data for '..player_name)
83
+ -- Any return is ignored, this is event is for cleaning up other data
84
+ end)
85
+
86
+ @usage-- Using Datastore Messaging
87
+ -- The message action can be used regardless of save_to_disk being set as no data is saved, but an external script is still required
88
+ -- These messages can be used to send data to other servers which doesnt need to be saved such as shouts or commands
89
+ -- Using messages is quite simple only using message and on_message
90
+ ExampleData:message(key, message)
91
+ ExampleData:on_message(function(key, message)
92
+ game.print('Received message '..message)
93
+ end)
94
+
95
+ @usage-- Combined Datastores
96
+ -- A combined datastore is a datastore which stores its data inside of another datastore
97
+ -- This means that the data is stored more efficiently in the external database and less requests need to be made
98
+ -- To understand how combined datastores work think of each key in the parent as a table where the sub datastore is a key in that table
99
+ -- Player data is the most used version of the combined datastore, below is how the player data module is setup
100
+ local PlayerData = Datastore.connect('PlayerData', true) -- saveToDisk
101
+ PlayerData:set_serializer(Datastore.name_serializer) -- use player name as key
102
+ PlayerData:combine('Statistics')
103
+ PlayerData:combine('Settings')
104
+ PlayerData:combine('Required')
105
+
106
+ -- You can then further combine datastores to any depth, below we add some possible settings and statistics that we might use
107
+ -- Although we dont in this example, each of these functions returns the datastore object which you should use as a local value
108
+ PlayerData.Settings:combine('Color')
109
+ PlayerData.Settings:combine('Quickbar')
110
+ PlayerData.Settings:combine('JoinMessage')
111
+ PlayerData.Statistics:combine('Playtime')
112
+ PlayerData.Statistics:combine('JoinCount')
113
+
114
+ -- Because sub datastore work just like a normal datastore you dont need any special code, using get and set will still return as if it wasnt a sub datastore
115
+ -- Things like the serializer and the datastore settings are always the same as the parent so you dont need to worry about setting up the serializer each time
116
+ -- And because save, request, and unload methods all point to the root datastore you are able to request and save your data as normal
117
+
118
+ -- If you used get_all on PlayerData this is what you would get:
119
+ {
120
+ Cooldude2606 = {
121
+ Settings = {
122
+ Color = 'ColorValue',
123
+ Quickbar = 'QuickbarValue',
124
+ JoinMessage = 'JoinMessageValue'
125
+ },
126
+ Statistics = {
127
+ Playtime = 'PlaytimeValue',
128
+ JoinCount = 'JoinCountValue'
129
+ }
130
+ }
131
+ }
132
+
133
+ -- If you used get_all on PlayerData.Settings this is what you would get:
134
+ {
135
+ Cooldude2606 = {
136
+ Color = 'ColorValue',
137
+ Quickbar = 'QuickbarValue',
138
+ JoinMessage = 'JoinMessageValue'
139
+ }
140
+ }
141
+
142
+ -- If you used get_all on PlayerData.Settings.Color this is what you would get:
143
+ {
144
+ Cooldude2606 = 'ColorValue'
145
+ }
146
+
147
+ ]]
148
+
149
+ local Storage = require("modules/exp_util/storage")
150
+
151
+ local DatastoreManager = {}
152
+ local Datastores = {} --- @type table<string, Datastore>
153
+ local Data = {}
154
+
155
+ --- @class Datastore
156
+ --- @field name string
157
+ --- @field value_name string
158
+ --- @field auto_save boolean
159
+ --- @field save_to_disk boolean
160
+ --- @field propagate_changes boolean
161
+ --- @field serializer function | false
162
+ --- @field parent Datastore | false
163
+ --- @field children table<string, Datastore>
164
+ --- @field metadata table
165
+ --- @field events table<string, function[]>
166
+ --- @field data table
167
+ local Datastore = {}
168
+
169
+ local copy = table.deep_copy
170
+ local trace = debug.traceback
171
+ local table_to_json = helpers.table_to_json
172
+ local json_to_table = helpers.json_to_table
173
+ local write_file = helpers.write_file
174
+
175
+ --- Save datastores in the global table
176
+ Storage.register(Data, function(tbl)
177
+ Data = tbl
178
+ for name, datastore in pairs(Datastores) do
179
+ datastore.data = Data[name]
180
+ end
181
+ end, function(tbl)
182
+ for name in pairs(Datastores) do
183
+ tbl[name] = tbl[name] or {}
184
+ end
185
+ end)
186
+
187
+ ----- Datastore Manager
188
+ -- @section datastoreManager
189
+
190
+ --- Metatable used on datastores
191
+ DatastoreManager.metatable = {
192
+ __index = function(self, key) return rawget(self.children, key) or rawget(Datastore, key) end,
193
+ __newidnex = function(_, _, _) error("Datastore can not be modified", 2) end,
194
+ __call = function(self, ...) return self:get(...) end,
195
+ }
196
+
197
+ --[[-- Make a new datastore connection, if a connection already exists then it is returned
198
+ @tparam string datastore_name The name that you want the new datastore to have, this can not have any whitespace
199
+ @tparam[opt=false] boolean saveToDisk When set to true, using the save method with write the data to datastore.pipe
200
+ @tparam[opt=false] boolean autoSave When set to true, using any method which modifies data will cause the data to be saved
201
+ @tparam[opt=false] boolean propagateChanges When set to true, using the save method will send the data to all other connected servers
202
+ @treturn table The new datastore connection that can be used to access and modify data in the datastore
203
+
204
+ @usage-- Connecting to the test datastore which will allow saving to disk
205
+ local ExampleData = Datastore.connect('ExampleData', true) -- saveToDisk
206
+
207
+ ]]
208
+ function DatastoreManager.connect(datastore_name, save_to_disk, autosave, propagate_changes)
209
+ if Datastores[datastore_name] then return Datastores[datastore_name] end
210
+ if package.lifecycle ~= package.lifecycle_stage.control then
211
+ -- Only allow this function to be called during the control stage
212
+ error("New datastore connection can not be created during runtime", 2)
213
+ end
214
+
215
+ local new_datastore = {
216
+ name = datastore_name,
217
+ value_name = datastore_name,
218
+ auto_save = autosave or false,
219
+ save_to_disk = save_to_disk or false,
220
+ propagate_changes = propagate_changes or false,
221
+ serializer = false,
222
+ parent = false,
223
+ children = {},
224
+ metadata = {},
225
+ events = {},
226
+ data = {},
227
+ }
228
+
229
+ Data[datastore_name] = new_datastore.data
230
+ Datastores[datastore_name] = new_datastore
231
+ return setmetatable(new_datastore, DatastoreManager.metatable)
232
+ end
233
+
234
+ --[[-- Make a new datastore that stores its data inside of another one
235
+ @tparam string datastore_name The name of the datastore that will contain the data for the new datastore
236
+ @tparam string subdatastore_name The name of the new datastore, this name will also be used as the key inside the parent datastore
237
+ @treturn table The new datastore connection that can be used to access and modify data in the datastore
238
+
239
+ @usage-- Setting up a datastore which stores its data inside of another datastore
240
+ local BarData = Datastore.combine('ExampleData', 'Bar')
241
+
242
+ ]]
243
+ function DatastoreManager.combine(datastore_name, subdatastore_name)
244
+ local datastore = assert(Datastores[datastore_name], "Datastore not found " .. tostring(datastore_name))
245
+ return datastore:combine(subdatastore_name)
246
+ end
247
+
248
+ --[[-- Ingest the result from a request, this is used through a rcon interface to sync data
249
+ @tparam string action The action that should be done, can be: remove, message, propagate, or request
250
+ @tparam string datastore_name The name of the datastore that should have the action done to it
251
+ @tparam string key The key of that datastore that is having the action done to it
252
+ @tparam string valueJson The json string for the value being ingested, remove does not require a value
253
+
254
+ @usage-- Replying to a data request
255
+ Datastore.ingest('request', 'ExampleData', 'TestKey', 'Foo')
256
+
257
+ ]]
258
+ function DatastoreManager.ingest(action, datastore_name, key, value_json)
259
+ local datastore = assert(Datastores[datastore_name], "Datastore ingest error, Datastore not found " .. tostring(datastore_name))
260
+ assert(type(action) == "string", "Datastore ingest error, Action is not a string got: " .. type(action))
261
+ assert(type(key) == "string", "Datastore ingest error, Key is not a string got: " .. type(key))
262
+
263
+ if action == "remove" then
264
+ datastore:raw_set(key)
265
+ elseif action == "message" then
266
+ local success, value = pcall(json_to_table, value_json)
267
+ if not success or value == nil then value = tonumber(value_json) or value_json end
268
+ datastore:raise_event("on_message", key, value)
269
+ elseif action == "propagate" or action == "request" then
270
+ local success, value = pcall(json_to_table, value_json)
271
+ if not success or value == nil then value = tonumber(value_json) or value_json end
272
+ local old_value = datastore:raw_get(key)
273
+ value = datastore:raise_event("on_load", key, value, old_value)
274
+ datastore:set(key, value)
275
+ end
276
+ end
277
+
278
+ --[[-- Debug, Use to get all datastores, or return debug info on a datastore
279
+ @tparam[opt] string datastore_name The name of the datastore to get the debug info of
280
+
281
+ @usage-- Get all the datastores
282
+ local datastores = Datastore.debug()
283
+
284
+ @usage-- Getting the debug info for a datastore
285
+ local debug_info = Datastore.debug('ExampleData')
286
+
287
+ ]]
288
+ function DatastoreManager.debug(datastore_name)
289
+ if not datastore_name then return Datastores end
290
+ local datastore = assert(Datastores[datastore_name], "Datastore not found " .. tostring(datastore_name))
291
+ return datastore:debug()
292
+ end
293
+
294
+ --[[-- Commonly used serializer, returns the name of the object
295
+ @tparam any rawKey The raw key that will be serialized, this can be things like player, force, surface, etc
296
+ @treturn string The name of the object that was passed
297
+
298
+ @usage-- Using the name serializer for your datastore
299
+ local ExampleData = Datastore.connect('ExampleData')
300
+ ExampleData:set_serializer(Datastore.name_serializer)
301
+
302
+ ]]
303
+ function DatastoreManager.name_serializer(raw_key)
304
+ return raw_key.name
305
+ end
306
+
307
+ ----- Datastore Internal
308
+ -- @section datastore-internal
309
+
310
+ --[[-- Debug, Get the debug info for this datastore
311
+ @treturn table The debug info for this datastore, contains stuff like parent, settings, children, etc
312
+
313
+ @usage-- Get the debug info for a datastore
314
+ local ExampleData = Datastore.connect('ExampleData')
315
+ local debug_info = ExampleData:debug()
316
+
317
+ ]]
318
+ function Datastore:debug()
319
+ local debug_info = {}
320
+
321
+ if self.parent then
322
+ debug_info.parent = self.parent.name
323
+ else
324
+ debug_info.settings = { auto_save = self.auto_save, save_to_disk = self.save_to_disk, propagate_changes = self.propagate_changes, serializer = not not self.serializer }
325
+ end
326
+
327
+ local children = {}
328
+ for name in pairs(self.children) do children[#children + 1] = name end
329
+
330
+ if #children > 0 then debug_info.children = children end
331
+
332
+ local events = {}
333
+ for name, handlers in pairs(self.events) do events[name] = #handlers end
334
+
335
+ if next(events) then debug_info.events = events end
336
+
337
+ if next(self.metadata) then debug_info.metadata = self.metadata end
338
+ debug_info.data = self:get_all()
339
+
340
+ return debug_info
341
+ end
342
+
343
+ --[[-- Internal, Get data following combine logic
344
+ @tparam string key The key to get the value of from this datastore
345
+ @tparam[opt=false] boolean fromChild If the get request came from a child of this datastore
346
+ @treturn any The value that was stored at this key in this datastore
347
+
348
+ @usage-- Internal, Get the data from a datastore
349
+ local value = self:raw_get('TestKey')
350
+
351
+ ]]
352
+ function Datastore:raw_get(key, from_child)
353
+ local data = self.data
354
+ if self.parent then
355
+ data = self.parent:raw_get(key, true)
356
+ key = self.value_name
357
+ end
358
+ local value = data[key]
359
+ if value ~= nil then return value end
360
+ if from_child then value = {} end
361
+ data[key] = value
362
+ return value
363
+ end
364
+
365
+ --[[-- Internal, Set data following combine logic
366
+ @tparam string key The key to set the value of in this datastore
367
+ @tparam any value The value that will be set at this key
368
+
369
+ @usage-- Internal, Set the value in a datastore
370
+ self:raw_set('TestKey', 'Foo')
371
+
372
+ ]]
373
+ function Datastore:raw_set(key, value)
374
+ if self.parent then
375
+ local data = self.parent:raw_get(key, true)
376
+ data[self.value_name] = value
377
+ else
378
+ self.data[key] = value
379
+ end
380
+ end
381
+
382
+ local function serialize_error(err) error("An error ocurred in a datastore serializer: " .. trace(err)) end
383
+ --[[-- Internal, Return the serialized key
384
+ @tparam any rawKey The key that needs to be serialized, if it is already a string then it is returned
385
+ @treturn string The key after it has been serialized
386
+
387
+ @usage-- Internal, Ensure that the key is a string
388
+ key = self:serialize(key)
389
+
390
+ ]]
391
+ function Datastore:serialize(raw_key)
392
+ if type(raw_key) == "string" then return raw_key end
393
+ assert(self.serializer, "Datastore does not have a serializer and received non string key")
394
+ local success, key = xpcall(self.serializer, serialize_error, raw_key)
395
+ return success and key or nil
396
+ end
397
+
398
+ --[[-- Internal, Writes an event to the output file to be saved and/or propagated
399
+ @tparam string action The action that should be wrote to datastore.pipe, can be request, remove, message, save, propagate
400
+ @tparam string key The key that the action is being preformed on
401
+ @tparam any value The value that should be used with the action
402
+
403
+ @usage-- Write a data request to datastore.pipe
404
+ self:write_action('request', 'TestKey')
405
+
406
+ @usage-- Write a data save to datastore.pipe
407
+ self:write_action('save', 'TestKey', 'Foo')
408
+
409
+ ]]
410
+ function Datastore:write_action(action, key, value)
411
+ local data = { action, self.name, key }
412
+ if value ~= nil then
413
+ data[4] = type(value) == "table" and table_to_json(value) or value
414
+ end
415
+ write_file("ext/datastore.out", table.concat(data, " ") .. "\n", true, 0)
416
+ end
417
+
418
+ ----- Datastore Local
419
+ -- @section datastore-local
420
+
421
+ --[[-- Create a new datastore which is stores its data inside of this datastore
422
+ @tparam string subdatastore_name The name of the datastore that will have its data stored in this datastore
423
+ @treturn table The new datastore that was created inside of this datastore
424
+
425
+ @usage-- Add a new sub datastore
426
+ local ExampleData = Datastore.connect('ExampleData')
427
+ local BarData = ExampleData:combine('Bar')
428
+
429
+ ]]
430
+ function Datastore:combine(subdatastore_name)
431
+ local new_datastore = DatastoreManager.connect(self.name .. "." .. subdatastore_name)
432
+ self.children[subdatastore_name] = new_datastore
433
+ new_datastore.value_name = subdatastore_name
434
+ new_datastore.serializer = self.serializer
435
+ new_datastore.auto_save = self.auto_save
436
+ new_datastore.parent = self
437
+ Data[new_datastore.name] = nil
438
+ new_datastore.data = nil
439
+ return new_datastore
440
+ end
441
+
442
+ --[[-- Set a callback that will be used to serialize keys which aren't strings
443
+ @tparam function callback The function that will be used to serialize non string keys passed as an argument
444
+
445
+ @usage-- Set a custom serializer, this would be the same as Datastore.name_serializer
446
+ local ExampleData = Datastore.connect('ExampleData')
447
+ ExampleData:set_serializer(function(rawKey)
448
+ return rawKey.name
449
+ end)
450
+
451
+ ]]
452
+ function Datastore:set_serializer(callback)
453
+ assert(type(callback) == "function", "Callback must be a function")
454
+ self.serializer = callback
455
+ end
456
+
457
+ --[[-- Set a default value to be returned by get if no other default is given, using will mean get will never return nil, set using the default will set to nil to save space
458
+ @tparam any value The value that will be deep copied by get if the value is nil and no other default is given
459
+ @tparam boolean allowSet When true if the default is passed as the value for set it will be set rather than setting nil
460
+
461
+ @usage-- Set a default value to be returned by get
462
+ local ExampleData = Datastore.connect('ExampleData')
463
+ ExampleData:set_default('Foo')
464
+
465
+ ]]
466
+ function Datastore:set_default(value, allow_set)
467
+ self.default = value
468
+ self.allow_set_to_default = allow_set
469
+ end
470
+
471
+ --[[-- Set metadata tags on this datastore which can be accessed by other scripts
472
+ @tparam table tags A table of tags that you want to set in the metadata for this datastore
473
+
474
+ @usage-- Adding metadata that could be used by a gui to help understand the stored data
475
+ local ExampleData = Datastore.connect('ExampleData')
476
+ ExampleData:set_metadata{
477
+ caption = 'Test Data',
478
+ tooltip = 'Data used for testing datastores',
479
+ type = 'table'
480
+ }
481
+
482
+ ]]
483
+ function Datastore:set_metadata(tags)
484
+ local metadata = self.metadata
485
+ for key, value in pairs(tags) do
486
+ metadata[key] = value
487
+ end
488
+ end
489
+
490
+ --[[-- Get a value from local storage, option to have a default value, do not edit the data returned as changes may not save, use update if you want to make changes
491
+ @tparam any key The key that you want to get the value of, must be a string unless a serializer is set
492
+ @tparam[opt] any default The default value that will be returned if no value is found in the datastore
493
+
494
+ @usage-- Get a key from the datastore, the default will be deep copied if no value exists in the datastore
495
+ local ExampleData = Datastore.connect('ExampleData')
496
+ local value = ExampleData:get('TestKey')
497
+
498
+ ]]
499
+ function Datastore:get(key, default)
500
+ key = self:serialize(key)
501
+ local value = self:raw_get(key)
502
+ if value ~= nil then return value end
503
+ return copy(default or self.default)
504
+ end
505
+
506
+ --[[-- Set a value in local storage, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
507
+ @tparam any key The key that you want to set the value of, must be a string unless a serializer is set
508
+ @tparam any value The value that you want to set for this key
509
+
510
+ @usage-- Set a value in the datastore, this will trigger on_update, if auto_save is true then will trigger save
511
+ local ExampleData = Datastore.connect('ExampleData')
512
+ ExampleData:set('TestKey', 'Foo')
513
+
514
+ ]]
515
+ function Datastore:set(key, value)
516
+ key = self:serialize(key)
517
+ local old_value = self:raw_get(key)
518
+ if value == self.default and not self.allow_set_to_default then
519
+ self:raw_set(key)
520
+ else
521
+ self:raw_set(key, value)
522
+ end
523
+ self:raise_event("on_update", key, value, old_value)
524
+ if self.auto_save then self:save(key) end
525
+ return value
526
+ end
527
+
528
+ --[[-- Increment the value in local storage, only works for number values, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
529
+ @tparam any key The key that you want to increment the value of, must be a string unless a serializer is set
530
+ @tparam[opt=1] number delta The amount that you want to increment the value by, can be negative or a decimal
531
+
532
+ @usage-- Increment a value in a datastore, the value must be a number or nil, if nil 0 is used as the start value
533
+ local ExampleData = Datastore.connect('ExampleData')
534
+ ExampleData:increment('TestNumber')
535
+
536
+ ]]
537
+ function Datastore:increment(key, delta)
538
+ key = self:serialize(key)
539
+ local value = self:raw_get(key) or 0
540
+ return self:set(key, value + (delta or 1))
541
+ end
542
+
543
+ local function update_error(err) log("An error occurred in datastore update:\n\t" .. trace(err)) end
544
+ --[[-- Use a function to update the value locally, will trigger on_update then on_save, save_to_disk and auto_save is required for on_save
545
+ @tparam any key The key that you want to apply the update to, must be a string unless a serializer is set
546
+ @tparam function callback The function that will be used to update the value at this key
547
+
548
+ @usage-- Using a function to update a value, if a value is returned then this will be the new value
549
+ local ExampleData = Datastore.connect('ExampleData')
550
+ ExampleData:increment('TestKey', function(key, value)
551
+ return value..value
552
+ end)
553
+
554
+ ]]
555
+ function Datastore:update(key, callback)
556
+ key = self:serialize(key)
557
+ local value = self:get(key)
558
+ local raw_value = self:raw_get(key)
559
+ local old_value = copy(self:raw_get(key))
560
+ local success, new_value = xpcall(callback, update_error, key, value)
561
+ if not success then
562
+ self:raw_set(key, old_value)
563
+ elseif new_value ~= nil then
564
+ self:set(key, new_value)
565
+ elseif raw_value == nil then
566
+ self:set(key, value)
567
+ else
568
+ self:raise_event("on_update", key, value, old_value)
569
+ if self.auto_save then self:save(key) end
570
+ end
571
+ end
572
+
573
+ --[[-- Remove a value locally and on the external source, works regardless of propagateChanges, requires save_to_disk for external changes
574
+ @tparam any key The key that you want to remove locally and externally, must be a string unless a serializer is set
575
+
576
+ @usage-- Remove a key locally and externally
577
+ local ExampleData = Datastore.connect('ExampleData')
578
+ ExampleData:remove('TestKey')
579
+
580
+ ]]
581
+ function Datastore:remove(key)
582
+ key = self:serialize(key)
583
+ local old_value = self:raw_get(key)
584
+ self:raw_set(key)
585
+ self:raise_event("on_update", key, nil, old_value)
586
+ if self.save_to_disk then self:write_action("remove", key) end
587
+ if self.parent and self.parent.auto_save then return self.parent:save(key) end
588
+ end
589
+
590
+ local function filter_error(err) log("An error ocurred in a datastore filter:\n\t" .. trace(err)) end
591
+ --[[-- Internal, Used to filter elements from a table
592
+ @tparam table tbl The table that will have the filter applied to it
593
+ @tparam[opt] function callback The function that will be used as a filter, if none giving then the provided table is returned
594
+ @treturn table The table which has only the key values pairs which passed the filter
595
+
596
+ @usage-- Internal, Filter a table by the values it contains, return true to keep the key value pair
597
+ local filtered_table = filter({5,3,4,1,2}, function(key, value)
598
+ return value > 2
599
+ end)
600
+
601
+ ]]
602
+ local function filter(tbl, callback)
603
+ if not callback then return tbl end
604
+ local rtn = {}
605
+ for key, value in pairs(tbl) do
606
+ local success, add = xpcall(callback, filter_error, key, value)
607
+ if success and add then rtn[key] = value end
608
+ end
609
+
610
+ return rtn
611
+ end
612
+
613
+ --[[-- Get all keys in this datastore, optional filter callback
614
+ @tparam[opt] function callback The filter function that can be used to filter the results returned
615
+ @treturn table All the data that is in this datastore, filtered if a filter was provided
616
+
617
+ @usage-- Get all the data in this datastore
618
+ local ExampleData = Datastore.connect('ExampleData')
619
+ local data = ExampleData:get_all()
620
+
621
+ @usage-- Get all the data in this datastore, with a filter
622
+ local ExampleData = Datastore.connect('ExampleData')
623
+ local data = ExampleData:get_all(function(key, value)
624
+ return type(value) == 'string'
625
+ end)
626
+
627
+ ]]
628
+ function Datastore:get_all(callback)
629
+ if not self.parent then
630
+ return filter(self.data, callback)
631
+ else
632
+ local data, value_name = {}, self.value_name
633
+ for key, value in pairs(self.parent:get_all()) do
634
+ data[key] = value[value_name]
635
+ end
636
+
637
+ return filter(data, callback)
638
+ end
639
+ end
640
+
641
+ --[[-- Update all keys in this datastore using the same update function
642
+ @tparam function callback The update function that will be applied to each key
643
+
644
+ @usage-- Get all the data in this datastore, with a filter
645
+ local ExampleData = Datastore.connect('ExampleData')
646
+ ExampleData:update_all(function(key, value)
647
+ return value..value
648
+ end)
649
+
650
+ ]]
651
+ function Datastore:update_all(callback)
652
+ local data = self:get_all()
653
+ for key, value in pairs(data) do
654
+ local old_value = copy(value)
655
+ local success, new_value = xpcall(callback, update_error, key, value)
656
+ if success and new_value ~= nil then
657
+ self:set(key, new_value)
658
+ else
659
+ self:raise_event("on_update", key, value, old_value)
660
+ if self.auto_save then self:save(key) end
661
+ end
662
+ end
663
+ end
664
+
665
+ ----- Datastore External
666
+ -- @section datastore-external
667
+
668
+ --[[-- Request a value from an external source, will trigger on_load when data is received
669
+ @tparam any key The key that you want to request from an external source, must be a string unless a serializer is set
670
+
671
+ @usage-- Request a key from an external source, on_load is triggered when data is received
672
+ local ExampleData = Datastore.connect('ExampleData')
673
+ ExampleData:request('TestKey')
674
+
675
+ ]]
676
+ function Datastore:request(key)
677
+ if self.parent then return self.parent:request(key) end
678
+ key = self:serialize(key)
679
+ self:write_action("request", key)
680
+ end
681
+
682
+ --[[-- Save a value to an external source, will trigger on_save before data is saved, save_to_disk must be set to true
683
+ @tparam any key The key that you want to save to an external source, must be a string unless a serializer is set
684
+
685
+ @usage-- Save a key to an external source, save_to_disk must be set to true for there to be any effect
686
+ local ExampleData = Datastore.connect('ExampleData')
687
+ ExampleData:save('TestKey')
688
+
689
+ ]]
690
+ function Datastore:save(key)
691
+ if self.parent then self.parent:save(key) end
692
+ if not self.save_to_disk then return end
693
+ key = self:serialize(key)
694
+ local value = self:raise_event("on_save", key, copy(self:raw_get(key)))
695
+ local action = self.propagate_changes and "propagate" or "save"
696
+ self:write_action(action, key, value)
697
+ end
698
+
699
+ --[[-- Save a value to an external source and remove locally, will trigger on_unload then on_save, save_to_disk is not required for on_unload
700
+ @tparam any key The key that you want to unload from the datastore, must be a string unless a serializer is set
701
+
702
+ @usage-- Unload a key from the datastore, get will now return nil and value will be saved externally if save_to_disk is set to true
703
+ local ExampleData = Datastore.connect('ExampleData')
704
+ ExampleData:unload('TestKey')
705
+
706
+ ]]
707
+ function Datastore:unload(key)
708
+ if self.parent then return self.parent:unload(key) end
709
+ key = self:serialize(key)
710
+ self:raise_event("on_unload", key, copy(self:raw_get(key)))
711
+ self:save(key)
712
+ self:raw_set(key)
713
+ end
714
+
715
+ --[[-- Use to send a message over the connection, works regardless of saveToDisk and propagateChanges
716
+ @tparam any key The key that you want to send a message over, must be a string unless a serializer is set
717
+ @tparam any message The message that you want to send to other connected servers, or external source
718
+
719
+ @usage-- Send a message to other servers on this key, can listen for messages with on_message
720
+ local ExampleData = Datastore.connect('ExampleData')
721
+ ExampleData:message('TestKey', 'Foo')
722
+
723
+ ]]
724
+ function Datastore:message(key, message)
725
+ key = self:serialize(key)
726
+ self:write_action("message", key, message)
727
+ end
728
+
729
+ --[[-- Save all the keys in the datastore, optional filter callback
730
+ @tparam[opt] function callback The filter function that can be used to filter the keys saved
731
+
732
+ @usage-- Save all the data in this datastore
733
+ local ExampleData = Datastore.connect('ExampleData')
734
+ local data = ExampleData:save_all()
735
+
736
+ @usage-- Save all the data in this datastore, with a filter
737
+ local ExampleData = Datastore.connect('ExampleData')
738
+ ExampleData:save_all(function(key, value)
739
+ return type(value) == 'string'
740
+ end)
741
+
742
+ ]]
743
+ function Datastore:save_all(callback)
744
+ local data = self:get_all(callback)
745
+ for key in pairs(data) do self:save(key) end
746
+ end
747
+
748
+ --[[-- Unload all the keys in the datastore, optional filter callback
749
+ @tparam[opt] function callback The filter function that can be used to filter the keys unloaded
750
+
751
+ @usage-- Unload all the data in this datastore
752
+ local ExampleData = Datastore.connect('ExampleData')
753
+ ExampleData:unload_all()
754
+
755
+ @usage-- Unload all the data in this datastore, with a filter
756
+ local ExampleData = Datastore.connect('ExampleData')
757
+ ExampleData:unload_all(function(key, value)
758
+ return type(value) == 'string'
759
+ end)
760
+
761
+ ]]
762
+ function Datastore:unload_all(callback)
763
+ local data = self:get_all(callback)
764
+ for key in pairs(data) do self:unload(key) end
765
+ end
766
+
767
+ ----- Events
768
+ -- @section events
769
+
770
+ local function event_error(err) log("An error ocurred in a datastore event handler:\n\t" .. trace(err)) end
771
+ --[[-- Internal, Raise an event on this datastore
772
+ @tparam string event_name The name of the event to raise for this datastore
773
+ @tparam string key The key that this event is being raised for
774
+ @tparam[opt] any value The current value that this key has, might be a deep copy of the value
775
+ @tparam[opt] any old_value The previous value that this key has, might be a deep copy of the value
776
+ @tparam[opt] string source Where this call came from, used to do event recursion so can be parent or child
777
+ @treturn any The value that is left after being passed through all the event handlers
778
+
779
+ @usage-- Internal, Getting the value that should be saved
780
+ value = self:raise_event('on_save', key, value)
781
+
782
+ ]]
783
+ function Datastore:raise_event(event_name, key, value, old_value, source)
784
+ -- Raise the event for the children of this datastore
785
+ if source ~= "child" and next(self.children) then
786
+ if type(value) ~= "table" then value = {} end
787
+ for value_name, child in pairs(self.children) do
788
+ local old_child_value = old_value and old_value[value_name] or nil
789
+ value[value_name] = child:raise_event(event_name, key, value[value_name], old_child_value, "parent")
790
+ end
791
+ end
792
+
793
+ -- Raise the event for this datastore
794
+ local handlers = self.events[event_name]
795
+ if handlers then
796
+ for _, handler in ipairs(handlers) do
797
+ local success, new_value = xpcall(handler, event_error, key, value, old_value)
798
+ if success and new_value ~= nil then value = new_value end
799
+ end
800
+ end
801
+
802
+ -- Raise the event for the parent of this datastore
803
+ if source ~= "parent" and self.parent then
804
+ local parent_value = self.parent:raw_get(key, true)
805
+ self.parent:raise_event(event_name, key, parent_value, parent_value, "child")
806
+ end
807
+
808
+ -- If this is the save event and the table is empty then return nil
809
+ if event_name == "on_save" and next(self.children) and not next(value) then return end
810
+ return value
811
+ end
812
+
813
+ --[[-- Internal, Returns a function which will add a callback to an event
814
+ @tparam string event_name The name of the event that this should create a handler adder for
815
+ @treturn function The function that can be used to add handlers to this event
816
+
817
+ @usage-- Internal, Get the function to add handlers to on_load
818
+ Datastore.on_load = event_factory('on_load')
819
+
820
+ ]]
821
+ local function event_factory(event_name)
822
+ return function(self, callback)
823
+ assert(type(callback) == "function", "Handler must be a function")
824
+ local handlers = self.events[event_name]
825
+ if not handlers then
826
+ self.events[event_name] = { callback }
827
+ else
828
+ handlers[#handlers + 1] = callback
829
+ end
830
+ end
831
+ end
832
+
833
+ --[[-- Register a callback that triggers when data is loaded from an external source, returned value is saved locally
834
+ @tparam function callback The handler that will be registered to the on_load event
835
+ @usage-- Adding a handler to on_load, returned value will be saved locally, can be used to deserialize the value beyond a normal json
836
+ local ExampleData = Datastore.connect('ExampleData')
837
+ ExampleData:on_load(function(key, value)
838
+ game.print('Test data loaded for: '..key)
839
+ end)
840
+ ]]
841
+ Datastore.on_load = event_factory("on_load")
842
+
843
+ --[[-- Register a callback that triggers before data is saved, returned value is saved externally
844
+ @tparam function callback The handler that will be registered to the on_load event
845
+ @usage-- Adding a handler to on_save, returned value will be saved externally, can be used to serialize the value beyond a normal json
846
+ local ExampleData = Datastore.connect('ExampleData')
847
+ ExampleData:on_save(function(key, value)
848
+ game.print('Test data saved for: '..key)
849
+ end)
850
+ ]]
851
+ Datastore.on_save = event_factory("on_save")
852
+
853
+ --[[-- Register a callback that triggers before data is unloaded, returned value is ignored
854
+ @tparam function callback The handler that will be registered to the on_load event
855
+ @usage-- Adding a handler to on_unload, returned value is ignored, can be used to clean up guis or local values related to this data
856
+ local ExampleData = Datastore.connect('ExampleData')
857
+ ExampleData:on_load(function(key, value)
858
+ game.print('Test data unloaded for: '..key)
859
+ end)
860
+ ]]
861
+ Datastore.on_unload = event_factory("on_unload")
862
+
863
+ --[[-- Register a callback that triggers when a message is received, returned value is ignored
864
+ @tparam function callback The handler that will be registered to the on_load event
865
+ @usage-- Adding a handler to on_message, returned value is ignored, can be used to receive messages from other connected servers without saving data
866
+ local ExampleData = Datastore.connect('ExampleData')
867
+ ExampleData:on_message(function(key, value)
868
+ game.print('Test data message for: '..key)
869
+ end)
870
+ ]]
871
+ Datastore.on_message = event_factory("on_message")
872
+
873
+ --[[-- Register a callback that triggers any time a value is changed, returned value is ignored
874
+ @tparam function callback The handler that will be registered to the on_load event
875
+ @usage-- Adding a handler to on_update, returned value is ignored, can be used to update guis or send messages when data is changed
876
+ local ExampleData = Datastore.connect('ExampleData')
877
+ ExampleData:on_update(function(key, value)
878
+ game.print('Test data updated for: '..key)
879
+ end)
880
+ ]]
881
+ Datastore.on_update = event_factory("on_update")
882
+
883
+ ----- Module Return
884
+ return DatastoreManager