mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
410
gamemodes/helix/plugins/saveents/libs/sv_saveents.lua
Normal file
410
gamemodes/helix/plugins/saveents/libs/sv_saveents.lua
Normal file
@@ -0,0 +1,410 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local ix = ix
|
||||
local ents = ents
|
||||
local ipairs = ipairs
|
||||
local IsValid = IsValid
|
||||
local ErrorNoHalt = ErrorNoHalt
|
||||
local util = util
|
||||
|
||||
ix.saveEnts = ix.saveEnts or {}
|
||||
ix.saveEnts.storedTypes = ix.saveEnts.storedTypes or {}
|
||||
ix.saveEnts.cache = ix.saveEnts.cache or {}
|
||||
|
||||
function ix.saveEnts:RegisterEntity(class, bDeleteOnRemove, bAutoSave, bAutoRestore, funcs)
|
||||
if (!class) then return end
|
||||
ix.saveEnts.storedTypes[class] = {
|
||||
class = class,
|
||||
bDeleteOnRemove = bDeleteOnRemove,
|
||||
bAutoSave = bAutoSave,
|
||||
bAutoRestore = bAutoRestore,
|
||||
OnSave = funcs.OnSave,
|
||||
OnRestorePreSpawn = funcs.OnRestorePreSpawn,
|
||||
OnRestore = funcs.OnRestore,
|
||||
ShouldSave = funcs.ShouldSave,
|
||||
ShouldRestore = funcs.ShouldRestore,
|
||||
OnDelete = funcs.OnDelete,
|
||||
}
|
||||
end
|
||||
|
||||
ix.saveEnts.batch = nil
|
||||
ix.saveEnts.batchNumber = 0
|
||||
ix.saveEnts.BATCH_SIZE = 500 -- amount of entities in a single 0.5 second batch, change as needed for performance/time it takes to save all entities
|
||||
|
||||
-- Helper function to check if an entity should save
|
||||
local function ShouldSave(entity, bNoAutoSave)
|
||||
if (IsValid(entity) and ix.saveEnts.storedTypes[entity:GetClass()] and (!bNoAutoSave or ix.saveEnts.storedTypes[entity:GetClass()].bAutoSave)) then
|
||||
local info = ix.saveEnts.storedTypes[entity:GetClass()]
|
||||
if (info.ShouldSave and !info.ShouldSave(entity)) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Helper function to save a batch of entities
|
||||
local function SaveBatch()
|
||||
local lib = ix.saveEnts
|
||||
local offset = lib.batchNumber * lib.BATCH_SIZE
|
||||
-- If we have a batch and at least one more item above our offset exists (as the for loop starts at offset + 1)
|
||||
if (lib.batch and #lib.batch > offset) then
|
||||
local bDone = false
|
||||
for i = 1, lib.BATCH_SIZE do
|
||||
-- Check if the item still exists
|
||||
if (lib.batch[offset + i]) then
|
||||
lib:SaveEntity(lib.batch[offset + i], true)
|
||||
else
|
||||
--Batch complete!
|
||||
bDone = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Batch not complete, and next batch has at least one more run?
|
||||
if (!bDone and #lib.batch > (offset + lib.BATCH_SIZE)) then
|
||||
-- Increase batch number and wait for the timer to run the next batch
|
||||
lib.batchNumber = lib.batchNumber + 1
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--Batch complete if we get this far!
|
||||
lib.batch = nil
|
||||
end
|
||||
|
||||
--- Saves all posible entities into the Database.
|
||||
-- If the entity does not exist in the DB, a new entry is added.
|
||||
-- If the entity does exist in the DB, the existing entry is updated.
|
||||
-- Note that classes which do not have autosave enabled are ignored, they are expected to handle their own saving already (via SaveClass or SaveEntity)
|
||||
function ix.saveEnts:SaveAll()
|
||||
if (ix.shuttingDown) then
|
||||
timer.Remove("ixSaveEnt")
|
||||
-- immediately save all date if the server is shutting down
|
||||
local entities = ents.GetAll()
|
||||
for i = 1, #entities do
|
||||
self:SaveEntity(entities[i])
|
||||
end
|
||||
else
|
||||
-- We are still running a batch, can't start a new one
|
||||
if (self.batch) then return end
|
||||
|
||||
local entities = ents.GetAll()
|
||||
self.batch = {}
|
||||
for k, entity in ipairs(entities) do
|
||||
if (ShouldSave(entity, true)) then
|
||||
self.batch[#self.batch + 1] = entity
|
||||
end
|
||||
end
|
||||
self.batchNumber = 0
|
||||
|
||||
-- Run batch 0 manually
|
||||
SaveBatch()
|
||||
-- If that doesn't cover it, set a timer
|
||||
if (self.batch and #self.batch > self.BATCH_SIZE) then
|
||||
-- We already did one run manually, so run timer for the amount of batches minus one
|
||||
timer.Create("ixSaveEnt", 0.5, math.ceil(#self.batch / self.BATCH_SIZE) - 1, SaveBatch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Saves all posible entities from a specific class into the Database.
|
||||
-- If the entity does not exist in the DB, a new entry is added.
|
||||
-- If the entity does exist in the DB, the existing entry is updated.
|
||||
-- Classes with autosave disabled will still be saved
|
||||
function ix.saveEnts:SaveClass(class)
|
||||
if (self.storedTypes[class]) then
|
||||
local entities = ents.FindByClass(class)
|
||||
for i = 1, #entities do
|
||||
self:SaveEntity(entities[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function to get the to-save data table
|
||||
local function CreateDataTable(entity)
|
||||
local info = ix.saveEnts.storedTypes[entity:GetClass()]
|
||||
if (!info) then return end
|
||||
|
||||
local data = {}
|
||||
data.pos = entity:GetPos()
|
||||
data.angles = entity:GetAngles()
|
||||
data.skin = entity:GetSkin()
|
||||
data.color = entity:GetColor()
|
||||
data.material = entity:GetMaterial()
|
||||
data.nocollide = entity:GetCollisionGroup() == COLLISION_GROUP_WORLD
|
||||
data.motion = false
|
||||
data.scale = entity.scaledSize
|
||||
|
||||
local physicsObject = entity:GetPhysicsObject()
|
||||
if (IsValid(physicsObject)) then
|
||||
data.motion = physicsObject:IsMotionEnabled()
|
||||
end
|
||||
|
||||
local materials = entity:GetMaterials()
|
||||
if (istable(materials)) then
|
||||
data.submats = {}
|
||||
|
||||
for k in pairs(materials) do
|
||||
if (entity:GetSubMaterial(k - 1) != "") then
|
||||
data.submats[k] = entity:GetSubMaterial(k - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local bodygroups = entity:GetBodyGroups()
|
||||
if (istable(bodygroups)) then
|
||||
data.bodygroups = {}
|
||||
|
||||
for _, v in pairs(bodygroups) do
|
||||
if (entity:GetBodygroup(v.id) > 0) then
|
||||
data.bodygroups[v.id] = entity:GetBodygroup(v.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (info.OnSave) then
|
||||
data = info.OnSave(entity, data) or data
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
-- Helper function to save an existing entity (was saved previously already)
|
||||
local function UpdateEntity(entity)
|
||||
if (entity.ixSaveEntsID) then
|
||||
local success, data = pcall(CreateDataTable, entity)
|
||||
if (!success) then
|
||||
ErrorNoHalt("[SAVEENTS-U-"..entity:GetClass().."] "..data)
|
||||
return
|
||||
end
|
||||
|
||||
local dataString = util.TableToJSON(data)
|
||||
local crc = util.CRC(dataString)
|
||||
|
||||
if (crc == entity.ixSaveEntsCRC) then
|
||||
return
|
||||
end
|
||||
|
||||
local query = mysql:Update("ix_saveents")
|
||||
query:Where("id", entity.ixSaveEntsID)
|
||||
query:Where("class", entity:GetClass())
|
||||
query:Where("map", game.GetMap())
|
||||
query:Update("data", dataString)
|
||||
query:Execute()
|
||||
|
||||
entity.ixSaveEntsCRC = crc
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function to save a new entity (not known to save system yet)
|
||||
local function CreateEntity(entity)
|
||||
if (!entity.ixSaveEntsID and !entity.ixSaveEntsBeingCreated) then
|
||||
local class = entity:GetClass()
|
||||
local success, data = pcall(CreateDataTable, entity)
|
||||
if (!success) then
|
||||
ErrorNoHalt("[SAVEENTS-C-"..class.."] "..data)
|
||||
return
|
||||
end
|
||||
|
||||
entity.ixSaveEntsBeingCreated = true
|
||||
|
||||
local dataString = util.TableToJSON(data)
|
||||
local insertQuery = mysql:Insert("ix_saveents")
|
||||
insertQuery:Insert("class", class)
|
||||
insertQuery:Insert("map", game.GetMap())
|
||||
insertQuery:Insert("data", dataString)
|
||||
insertQuery:Insert("deleted", 0)
|
||||
insertQuery:Callback(function(result, status, id)
|
||||
if (IsValid(entity) and entity.ixSaveEntsBeingCreated) then
|
||||
entity.ixSaveEntsID = id
|
||||
entity.ixSaveEntsCRC = util.CRC(dataString)
|
||||
entity.ixSaveEntsBeingCreated = nil
|
||||
else
|
||||
-- Entity got deleted/unsaved before creation finished, so lets nuke it
|
||||
ix.saveEnts:DeleteEntityByID(id, class)
|
||||
end
|
||||
end)
|
||||
insertQuery:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
-- Saves a single entity into the Database.
|
||||
-- If the entity does not exist in the DB, a new entry is added.
|
||||
-- If the entity does exist in the DB, the existing entry is updated.
|
||||
-- Setting bNoAutoSave to true causes the entity to not be saved if its class isn't marked for auto-save
|
||||
function ix.saveEnts:SaveEntity(entity, bNoAutoSave)
|
||||
if (!self.dbLoaded) then
|
||||
self.cache[#self.cache + 1] = {entity, bNoAutoSave}
|
||||
return
|
||||
end
|
||||
|
||||
if (ShouldSave(entity, bNoAutoSave)) then
|
||||
if (entity.ixSaveEntsID) then
|
||||
UpdateEntity(entity)
|
||||
else
|
||||
CreateEntity(entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Restore all entities saved in the DB, for as far as they weren't restored already
|
||||
function ix.saveEnts:RestoreAll(class)
|
||||
local restoredEnts = {}
|
||||
for k, v in ipairs(ents.GetAll()) do
|
||||
if (v.ixSaveEntsID) then
|
||||
restoredEnts[v.ixSaveEntsID] = v:GetClass()
|
||||
end
|
||||
end
|
||||
|
||||
local selectQuery = mysql:Select("ix_saveents")
|
||||
selectQuery:Where("map", game.GetMap())
|
||||
selectQuery:Where("deleted", 0)
|
||||
if (class) then
|
||||
selectQuery:Where("class", class)
|
||||
end
|
||||
selectQuery:Callback(function(result)
|
||||
if (!result) then return end
|
||||
for k, v in ipairs(result) do
|
||||
-- entity was already restored
|
||||
if (restoredEnts[v.id]) then
|
||||
if (restoredEnts[v.id] != v.class) then
|
||||
ErrorNoHalt("[SaveEnts] L'entité de restauration existe déjà avec une classe incompatible ! DBID:"..v.id.."; "..v.class.." (DB) vs "..restoredEnts[v.id].." (spawned)")
|
||||
end
|
||||
continue
|
||||
end
|
||||
|
||||
-- this class is no longer registered (likely plugin failed to load, or it was removed)
|
||||
local info = self.storedTypes[v.class]
|
||||
if (!info) then continue end
|
||||
|
||||
-- do not automatically restore unless we are specifically restoring this class
|
||||
if (!info.bAutoRestore and class != v.class) then continue end
|
||||
|
||||
local data = util.JSONToTable(v.data)
|
||||
if (!data) then continue end
|
||||
|
||||
if (info.ShouldRestore) then
|
||||
local success, bShouldRestore, bDelete = pcall(info.ShouldRestore, data)
|
||||
if (!success) then
|
||||
ErrorNoHalt("[SAVEENTS-L1-"..v.class.."] "..bShouldRestore)
|
||||
continue
|
||||
end
|
||||
|
||||
if (!bShouldRestore) then
|
||||
if (bDelete) then
|
||||
self:DeleteEntityByID(v.id, v.class)
|
||||
end
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
local entity = ents.Create(v.class)
|
||||
entity.ixSaveEntsID = v.id
|
||||
entity.ixSaveEntsCRC = util.CRC(v.data)
|
||||
|
||||
if (data.pos) then entity:SetPos(data.pos) end
|
||||
if (data.angles) then entity:SetAngles(data.angles) end
|
||||
if (data.skin) then entity:SetSkin(data.skin) end
|
||||
if (data.color) then entity:SetColor(data.color) end
|
||||
if (data.material) then entity:SetMaterial(data.material) end
|
||||
if (data.scale) then
|
||||
timer.Simple(3, function()
|
||||
ix.scalestuff:ScaleEntity(entity, data.scale)
|
||||
end)
|
||||
end
|
||||
|
||||
if (info.OnRestorePreSpawn) then
|
||||
local success, err = pcall(info.OnRestorePreSpawn, entity, data)
|
||||
if (!success) then
|
||||
ErrorNoHalt("[SAVEENTS-L2-"..v.class.."] "..err)
|
||||
continue
|
||||
end
|
||||
end
|
||||
entity:Spawn()
|
||||
|
||||
if (data.nocollide) then entity:SetCollisionGroup(COLLISION_GROUP_WORLD) end
|
||||
|
||||
if (istable(v.submats)) then
|
||||
for k2, v2 in pairs(v.submats) do
|
||||
if (!isnumber(k2) or !isstring(v2)) then
|
||||
continue
|
||||
end
|
||||
|
||||
entity:SetSubMaterial(k2 - 1, v2)
|
||||
end
|
||||
end
|
||||
|
||||
if (istable(v.bodygroups)) then
|
||||
for k2, v2 in pairs(v.bodygroups) do
|
||||
entity:SetBodygroup(k2, v2)
|
||||
end
|
||||
end
|
||||
|
||||
if (data.motion != nil) then
|
||||
local physicsObject = entity:GetPhysicsObject()
|
||||
if (IsValid(physicsObject)) then
|
||||
physicsObject:EnableMotion(data.motion)
|
||||
end
|
||||
end
|
||||
|
||||
if (info.OnRestore) then
|
||||
local success, err = pcall(info.OnRestore, entity, data)
|
||||
if (!success) then
|
||||
ErrorNoHalt("[SAVEENTS-L3-"..v.class.."] "..err)
|
||||
continue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("PostSaveEntsRestore", class)
|
||||
end)
|
||||
selectQuery:Execute()
|
||||
end
|
||||
|
||||
-- Delete the save of an entity
|
||||
function ix.saveEnts:DeleteEntity(entity)
|
||||
if (ix.shuttingDown) then return end
|
||||
if (!IsValid(entity)) then return end
|
||||
|
||||
if (entity.ixSaveEntsBeingCreated) then
|
||||
-- stop creation, this will cause it to be deleted after creation is finished
|
||||
entity.ixSaveEntsBeingCreated = nil
|
||||
return
|
||||
end
|
||||
|
||||
if (!entity.ixSaveEntsID) then
|
||||
return
|
||||
end
|
||||
|
||||
local class = entity:GetClass()
|
||||
self:DeleteEntityByID(entity.ixSaveEntsID, class)
|
||||
|
||||
entity.ixSaveEntsID = nil
|
||||
if (self.storedTypes[class] and self.storedTypes[class].OnDelete) then
|
||||
self.storedTypes[class].OnDelete(entity)
|
||||
end
|
||||
end
|
||||
|
||||
-- Manually delete the save of an entity - you should use DeleteEntity unless you know what you are doing
|
||||
function ix.saveEnts:DeleteEntityByID(id, class)
|
||||
local deleteQuery = mysql:Update("ix_saveents")
|
||||
deleteQuery:Where("id", id)
|
||||
deleteQuery:Where("map", game.GetMap())
|
||||
if (class) then
|
||||
--Extra safety check so you can't accidentally delete the wrong entity
|
||||
deleteQuery:Where("class", class)
|
||||
end
|
||||
deleteQuery:Update("deleted", os.time())
|
||||
deleteQuery:Execute()
|
||||
end
|
||||
68
gamemodes/helix/plugins/saveents/sh_plugin.lua
Normal file
68
gamemodes/helix/plugins/saveents/sh_plugin.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
PLUGIN.name = "Save Entities"
|
||||
PLUGIN.description = "Saves entities into the database, creating a record per entity."
|
||||
PLUGIN.author = "Gr4Ss"
|
||||
|
||||
ix.util.Include("sv_plugin.lua")
|
||||
|
||||
ix.config.Add("SaveEntsOldLoadingEnabled", false, "If the old (file-based) entity loading should be used, or the new (DB-based) loading.", nil, {
|
||||
category = "Autres"
|
||||
})
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - SaveEnts",
|
||||
MinAccess = "superadmin"
|
||||
})
|
||||
|
||||
|
||||
ix.command.Add("SaveEntsSave", {
|
||||
description = "Sauvegarde toutes les entités d'une classe spécifique (ou lance la sauvegarde automatique si aucune classe n'est fournie).",
|
||||
arguments = {
|
||||
bit.bor(ix.type.string, ix.type.optional)
|
||||
},
|
||||
privilege = "SaveEnts",
|
||||
OnRun = function(self, client, class)
|
||||
if (class) then
|
||||
if (!ix.saveEnts.storedTypes[class]) then
|
||||
return class.." n'est pas une classe saveEnts valide !"
|
||||
end
|
||||
|
||||
ix.saveEnts:SaveClass(class)
|
||||
return "Sauvegarde de toutes les entités de la classe "..class.."!"
|
||||
else
|
||||
ix.saveEnts:SaveAll()
|
||||
return "Sauvegardé toutes les entités !"
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
ix.command.Add("SaveEntsLoad", {
|
||||
description = "Charge toutes les entités d'une classe spécifique (ou exécute le chargement automatique si aucune classe n'est fournie). Les entités déjà chargées sont ignorées.",
|
||||
arguments = {
|
||||
bit.bor(ix.type.string, ix.type.optional)
|
||||
},
|
||||
privilege = "SaveEnts",
|
||||
OnRun = function(self, client, class)
|
||||
if (class) then
|
||||
if (!ix.saveEnts.storedTypes[class]) then
|
||||
return class.." n'est pas une classe saveEnts valide !"
|
||||
end
|
||||
|
||||
ix.saveEnts:RestoreAll(class)
|
||||
return "Chargé toutes les entités de la classe "..class.."!"
|
||||
else
|
||||
ix.saveEnts:RestoreAll()
|
||||
return "Chargé toutes les entités !"
|
||||
end
|
||||
end,
|
||||
})
|
||||
149
gamemodes/helix/plugins/saveents/sv_plugin.lua
Normal file
149
gamemodes/helix/plugins/saveents/sv_plugin.lua
Normal file
@@ -0,0 +1,149 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PLUGIN = PLUGIN
|
||||
local ix = ix
|
||||
|
||||
function PLUGIN:DatabaseConnected()
|
||||
local query = mysql:Create("ix_saveents")
|
||||
query:Create("id", "INT UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:Create("class", "VARCHAR(255) NOT NULL")
|
||||
query:Create("map", "VARCHAR(255) NOT NULL")
|
||||
query:Create("data", "TEXT NOT NULL")
|
||||
query:Create("deleted", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:PrimaryKey("id")
|
||||
query:Execute()
|
||||
|
||||
ix.saveEnts.dbLoaded = true
|
||||
for k, v in ipairs(ix.saveEnts.cache) do
|
||||
ix.saveEnts:SaveEntity(v[1], v[2])
|
||||
end
|
||||
|
||||
if (ix.config.Get("SaveEntsOldLoadingEnabled")) then
|
||||
local updateQuery = mysql:Update("ix_saveents")
|
||||
updateQuery:Where("deleted", 0)
|
||||
updateQuery:Where("map", game.GetMap())
|
||||
updateQuery:Update("deleted", os.time())
|
||||
updateQuery:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:SaveData()
|
||||
ix.saveEnts:SaveAll()
|
||||
end
|
||||
|
||||
function PLUGIN:LoadData()
|
||||
if (!ix.config.Get("SaveEntsOldLoadingEnabled")) then
|
||||
ix.saveEnts:RestoreAll()
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:EntityRemoved(entity)
|
||||
if (ix.shuttingDown) then return end
|
||||
if (!entity.ixSaveEntsID and !entity.ixSaveEntsBeingCreated) then return end
|
||||
|
||||
local class = entity:GetClass()
|
||||
if (ix.saveEnts.storedTypes[class] and ix.saveEnts.storedTypes[class].bDeleteOnRemove) then
|
||||
ix.saveEnts:DeleteEntity(entity)
|
||||
end
|
||||
end
|
||||
|
||||
function PLUGIN:PhysgunDrop(client, entity)
|
||||
ix.saveEnts:SaveEntity(entity, true)
|
||||
end
|
||||
|
||||
function PLUGIN:InitializedPlugins()
|
||||
hook.SafeRun("RegisterSaveEnts")
|
||||
end
|
||||
|
||||
local persistWhitelist = {
|
||||
["prop_physics"] = true,
|
||||
["prop_effect"] = true,
|
||||
}
|
||||
function PLUGIN:CanProperty(client, property, entity)
|
||||
if (property == "persist" and IsValid(entity) and ix.saveEnts.storedTypes[entity:GetClass()] and !persistWhitelist[entity:GetClass()]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
WHAT THIS DOES
|
||||
This plugin saves and restores entities registered with it. Registering entities overal is easier than writing your own save/restore code, as a lot of stuff is already handled by the plugin. For most use cases, you only must tell the plugin what custom data you want to store and how it should be restored. Everything else is done by the plugin: grabbing generic data (position, angles, skin, material, frozen state), writing it into the DB, tracking entities, restoring them on load. The plugin will store the data entity per entity in the DB rather than using a big JSON table for all entities of the same class in a file. This is less risky for data to get lost: entities are only removed from the DB if they specifically get deleted - 'bad' saves aren't possible (e.g. in case of a bad load, causing the save file to be overwritten with an empty table) unless an entity is in a bad state.
|
||||
|
||||
You can also easily save entities proactively if your data on them changes by calling the SaveEntity function. This immediately updates the save for your entity, and means there is no risk of a desync in case of a crash before the next SaveData run. As the plugin can write into the DB per entity, this is much more efficient than helix's way of finding all entities of a class and writing a giant table into a file every time one little value changes.
|
||||
|
||||
The plugin itself is already proactive in saving an entity every time it is released by the physgun (assuming position or angles changed). Deleted entities also get their save automatically removed.
|
||||
|
||||
Further more there is an optimization: if no data changed, no write into the DB is done. This avoids hammering the DB updating 1000's of entities one by one when SaveData runs. A simple CRC check is used for this. This also means that proactively saving entities is good: it causes the save to happen before SaveData is run, helping spread out the load on the DB (better a write every second than 300 writes at the same time every 5 minutes). SaveData also checks entities in batches every 0.5 seconds, so you don't get a giant lag spike on the frame where the save happens.
|
||||
|
||||
tl;dr use this plugin, it is easier, better and more performant
|
||||
|
||||
HOW TO USE THIS
|
||||
|
||||
1) Register your entity class by calling: ix.saveEnts:RegisterEntity(class, bDeleteOnRemove, bAutoSave, bAutoRestore, funcs)
|
||||
Easiest to do this in the RegisterSaveEnts hook to be sure this plugin has loaded, but do it before entities are restored.
|
||||
|
||||
MANDATORY
|
||||
class: the class of your entity as a string
|
||||
|
||||
SEMI-OPTIONAL (defaults to false if not provided, you usually want them as true though)
|
||||
bDeleteOnRemove: automatically deletes saved data if the entity is removed, preventing it from respawning
|
||||
bAutoSave: include this entity in the periodic auto-save
|
||||
bAutoRestore: automatically restore this class with all the other entities
|
||||
|
||||
OPTIONAL FIELDS, these all are in the 'funcs' argument
|
||||
OnSave(entity, data): function to set data on save, either change the data table or return a new data table (overrides the passed data table, they do not get merged!). You can also edit some of the default data in here, or remove it if you do not wish for it to be automatically restored. Note that the library already takes care of pos, angles, skin & motion (clear these fields from the data if you don't want them saved)
|
||||
OnRestore(entity, data): restore data on your entity using the data table
|
||||
ShouldSave(entity): return nil/false if you do not wish to save your entity
|
||||
ShouldRestore(data): return nil/false if you do not wish to restore this data
|
||||
OnDelete(entity): called if an entity's saved data is deleted (this isn't necessarily when the entity is deleted, depending on how you use the save system)
|
||||
|
||||
Example for a simple entity:
|
||||
ix.saveEnts:RegisterEntity("ix_example", true, true, true, {
|
||||
OnSave = function(entity, data)
|
||||
data.someField = entity.someField
|
||||
end,
|
||||
OnRestore = function(entity, data)
|
||||
entity:RestoreSomeField(data.someField)
|
||||
end
|
||||
})
|
||||
|
||||
MAKE SURE THAT YOUR SAVE/RESTORE FUNCTIONS DO NOT ERROR! This prevents other stuff from saving/loading! Always test when you make changes or add stuff!!!
|
||||
|
||||
IF YOU MAKE CHANGES TO THESE FUNCTIONS, KEEP OnRestore BACKWARDS COMPATIBLE WITH THE DATA IN THE DB! Otherwise you get errors loading in your items, and this prevents the new OnSave from being run...
|
||||
|
||||
The above takes care of the vast majority of the work.
|
||||
|
||||
2) You have a more complex use case, you can manually call some functions (from your code, or using lua_run):
|
||||
ix.saveEnts:SaveAll(): saves all entities as far as their classes are registered. This is done in batches and may need some time to run, calling it while it is still running has no effect
|
||||
ix.saveEnts:SaveClass(class): saves all entities of the given class (assuming the class is registered with the plugin), if a class is given the bAutoRestore is ignored should it be nil/false
|
||||
ix.saveEnts:SaveEntity(entity): creates the save for the entity or updates its existing save
|
||||
ix.saveEnts:RestoreAll(class): restore all entities, shouldn't cause issues with already loaded entities. Class is optional to filter only to a class. Already restored entities that weren't removed won't be recreated.
|
||||
ix.saveEnts:DeleteEntity(entity): deletes the saved data for the entity (does not remove the entity itself)
|
||||
ix.saveEnts:DeleteEntityByID(id, class): manually deletes the saved data for an ID - USE WITH CAUTION (e.g. ensure there is no entity with the ixSaveEntsID left), doesn't call the OnDelete callback either, class is optional for extra safety
|
||||
|
||||
|
||||
SOMETHING FUCKED UP AND YOU NEED TO RESTORE
|
||||
-Entities were deleted:
|
||||
-Reset the 'deleted' column in the DB back to 0 (e.g. for a given time, interval or class) and do 'lua_run ix.saveEnts:RestoreAll()' or maprestart
|
||||
-Note that while ix is shutting down, entity deletion shouldn't happen in the DB for any reason
|
||||
-An error caused entities to not restore:
|
||||
-Fix the error and maprestart, unless the entities were explicitly deleted, their data is still in the DB
|
||||
-Saved data got corrupted:
|
||||
-You cannot recover this data except from a DB backup
|
||||
-Use ShouldSave hooks to ensure only correct data gets saved (e.g. do not save containers with inventory ID 0 as this is never correct)
|
||||
-Entities loaded from another source and got duplicated:
|
||||
-You will manually have to delete the duplicate entities, or shut the server down and delete them from the DB (based on their ID for example)
|
||||
-If other sources load in entities on top of saveEnts loading them in, the saveEnts plugin considers these 'new entities' and makes a new additional save for them
|
||||
|
||||
KEEP IN MIND:
|
||||
-Not restoring entities means they will stay in the DB! If an item shouldn't be restored, make sure to not save it... if it was saved anyway, delete it upon restoring it
|
||||
--]]
|
||||
Reference in New Issue
Block a user