How do I save a table to an entity so it loads across different game sessions in single player?

As the title suggests, I am trying to save a table to an entity in single player so it saves with the default gm_save, and would be later readable when the save is loaded, even after Garry’s Mod closes.

I’ve tried putting the table in entity.savetable = tableIwanttosave, I’ve tried using the duplicator.StoreEntityModifier function, but nothing works (the duplicator modifier stops working when the map is reloaded). I might be doing something wrong, and I desperately need help on this.

I would appreciate guidance, and even better an example of how to solve this.

Thanks!

It’s obviously not as simple as just doing ‘savetable’.

You need an sql(suggested) or text file database with unique identifier for every player.
Function to save changes to database, function to load from database and apply to a player.

Alright, I’m looking into sql now, but I can’t find a way to save an entire table, only strings. Is there a way to save an entire table?

Additionally, the reason I figured I could just do ent.savetable was because it WORKED here:


AddCSLuaFile()


TOOL.Category = "Poser"
TOOL.Name = "#tool.effectresizer.name"


if CLIENT then
	language.Add("tool.effectresizer.name", "Effect Resizer")
	language.Add("tool.effectresizer.desc", "Resize Effects")
	language.Add("tool.effectresizer.0", "Left Click to apply, Right Click to copy, Reload to reset")
end


TOOL.ClientConVar[ "scale" ] = 1
TOOL.ClientConVar[ "scalex" ] = 1
TOOL.ClientConVar[ "scaley" ] = 1
TOOL.ClientConVar[ "scalez" ] = 1




if SERVER then util.AddNetworkString("EffectResizeEntityMatrix") end
if CLIENT then
	net.Receive("EffectResizeEntityMatrix", function()
		local effect = net.ReadEntity()
		if !IsValid(effect) then return end


		local mat = Matrix()
		
		timer.Simple(0.25, function()	-- gotta delay this so that the NWVector arrives, how shitty is that?
			local scale = effect:GetNWVector("RenderMultiplyMatrixScale", Vector( 1, 1, 1 ))
			
			local nochange = (scale.x == 1 and scale.y == 1 and scale.z == 1)	-- disable matrix scaling altogether


			local xflat = (scale.x == 0) and 1 or 0
			local yflat = (scale.y == 0) and 1 or 0
			local zflat = (scale.z == 0) and 1 or 0


			local tooflat = (xflat + yflat + zflat > 1)	-- only one axis is allowed to be flat (and even that's not really recommended)


			if nochange or tooflat then
				effect:DisableMatrix("RenderMultiply")
			else
				local mat = Matrix()
				mat:Scale( scale )
				effect:EnableMatrix( "RenderMultiply", mat )
			end
		end)
	end)
end


function TOOL:LeftClick( trace )
	local ent = trace.Entity
	if !IsValid(ent) then return false end
	if ent:GetClass() != "prop_effect" then return false end
	if CLIENT then return true end


	local effect = ent.AttachedEntity
	if !IsValid(effect) then return false end -- sadly can't be synced with client


	effect:SetModelScale(self:GetClientNumber("scale"))


	local scale = Vector(self:GetClientNumber("scalex"), self:GetClientNumber("scaley"), self:GetClientNumber("scalez"))
	ent.scalevec = scale
	effect:SetNWVector("RenderMultiplyMatrixScale", scale)


	net.Start("EffectResizeEntityMatrix")
		net.WriteEntity(effect)
	net.Broadcast()


	return true
end


function TOOL:RightClick( trace )
	local ent = trace.Entity
	if !IsValid(ent) then return false end
	if ent:GetClass() != "prop_effect" then return false end
	if CLIENT then return true end


	local effect = ent.AttachedEntity
	if !IsValid(effect) then return false end -- sadly can't be synced with client


	self:GetOwner():ConCommand( "effectresizer_scale " .. effect:GetModelScale())
	return true
end


function TOOL:Reload( trace )
	local ent = trace.Entity
	for k, effects in pairs(ents.GetAll()) do if effects:GetClass() == "prop_effect" and IsValid(effects.AttachedEntity) then
		print (effects)
		effects.AttachedEntity:SetNWVector("RenderMultiplyMatrixScale", effects.scalevec)
		net.Start("EffectResizeEntityMatrix", false)
		net.WriteEntity(effects.AttachedEntity)
		net.Broadcast()
	end end
	if !IsValid(ent) then return false end
	if ent:GetClass() != "prop_effect" then return false end
	if CLIENT then return true end


	local effect = ent.AttachedEntity
	if !IsValid(effect) then return false end -- sadly can't be synced with client


	effect:SetModelScale(1)
	
	local scale = Vector(1, 1, 1)
	effect:SetNWVector("RenderMultiplyMatrixScale", scale)


	net.Start("EffectResizeEntityMatrix")
		net.WriteEntity(effect)
	net.Broadcast()


	return true
end


function TOOL.BuildCPanel(CPanel)
	CPanel:NumSlider("Scale: ", "effectresizer_scale", 0, 10)
	CPanel:NumSlider("X Scale: ", "effectresizer_scalex", 0, 1)
	CPanel:NumSlider("Y Scale: ", "effectresizer_scaley", 0, 1)
	CPanel:NumSlider("Z Scale: ", "effectresizer_scalez", 0, 1)
end

This belongs to an effect resizer tool. Please ignore the code quality, this was and still is a dirty WIP. I wanted effects’ scales to save across game sessions, so I added ‘ent.scalevec = scale’ under the TOOL:LeftClick() function, and additionally network the vector. Later on, on TOOL:Reload(), I’m using effects.scalevec to re-acquire the vector and apply it. Don’t ask me how or why this works, but it just does, saved effects get resized to their right scale when I hit reload, even after closing the game.

If anyone can shed some light onto why they think this^ works, it would be amazing, because lua-noob me doesn’t get this, even if it made sense to me when writing it.

I know this is a bit funny, but my request is genuine and desperate.

Look up util.JSONToTable and util.TableToJSON

Ye, I had a table in my game-modes database that was:

source, target, data - and data was a JSON string. Works very well, as you can just turn it back to a Lua table using util.JSONToTable().

I usually wouldn’t agree with having a column in a database that holds variable data - as you can imagine not the best database design structure - however in some cases where you have to use sql and the data has to be varied, it works well. The only issue being you can’t run queries on the data ( using purely sql ).

Vioxtar is looking for a way to save data to an entity in a way that would be preserved in a save file in singleplayer. SQL or file. Write sounds like the wrong approach, to me.

Taking the example in

duplicator.RegisterEntityModifier, if you’re using a tool that makes props unbreakable, and you use it in singleplayer, save, close the game, and load your save the next day - you expect the unbreakable props to remain unbreakable. According to Vioxtar though, the aforelinked function doesn’t accomplish this. (source: private chat)

For the record I don’t know the answer and in fact I would love to hear an answer as I have somewhere that needs it myself. I just think you guys are pointing in the wrong direction.

[editline]27th April 2016[/editline]

Okay, I just did some testing.

[lua]ent.YourCustomVariable = “YourCustomValue”[/lua]

This is preserved across save/load, only on scripted entities. Engine entities (like prop_physics) throw it away on load (or if you want to get technical, on save).

That’s why the lua dump Viox posted above works - because prop_effect is a scripted entity.

Vioxtar needs to save custom data on props.

As for actual solutions… I dunno.

I would use the sql library, the server and client both have one that is seperate from each other.

Build constructor/deconstructor functions and viola!

does sql get saved with the game in singleplayer?

Yes, you have a cl.db and sv.db in your steamapps/common/GarrysMod/garrysmod/ directory.

With the sql library, you do not have to worry about creating a database, just create whatever table you want and it will be saved on either server or client depending on where you called the code.

[editline]30th April 2016[/editline]

As a bonus, ill even share with you my handy little sql table setup function. This should be placed in the shared.lua and you pass it an argument from the server or client, but do note the databases are seperate so calling this from the server does not affect the client and vice versa.



--code for the shared.lua
function SetupSqlTables(tableCheck)
	local finalQuery
	local thisTable
	local returnTable = {}
	local thisField
	local sqlReturn 
	for i = 1,table.Count(tableCheck) do
		thisTable = tableCheck*
		if(!sql.TableExists(thisTable[1])) then
			finalQuery = "CREATE TABLE " .. thisTable[1] .. " ("
			for j = 2,table.Count(thisTable) do
				thisField = thisTable[j]
				finalQuery = finalQuery .. " " .. thisField .. "," 
			end
			finalQuery = string.sub( finalQuery , 1 , #finalQuery - 1 ) .. " );"
			sqlReturn = sql.Query( finalQuery )
			if(sqlReturn == false) then
				table.insert(returnTable,("(BAD)Failed to create table, error: " .. sql.LastError()))
			elseif(sqlReturn == nil) then
					print(finalQuery)
				table.insert(returnTable,("(GOOD)Table \"" .. thisTable[1] .. "\" created successfully"))
			end
		else
			table.insert(returnTable,("(GOOD)Table \"" .. thisTable[1] .. "\" already exists"))
		end
	end
return returnTable
end


Then to check if tables exists and create them if they do not:



local tableCheck = {     --parent table
		{"paloozrp_player_data","Steamid varchar(255)","Money int"},  --table whos first entry is the table name, followed by entries of <key name in table><space><value type>
		{"paloozrp_server_settings","SettingKey string","SettingValue string"}		
	}
	print("

" .. table.ToString(SetupSqlTables(tableCheck),"Server Table Check",true) .. "

")  -- the SetupSqlTables(someTable) returns some strings giving you feedback


[editline]30th April 2016[/editline]

Your server/client will then print to console something like:

Server Table Check = {
“(GOOD)Table “paloozrp_player_data” already exists”,
“(GOOD)Table “paloozrp_server_settings” already exists”,
}