How to fix a class or table being invalid

making my short steamID function I ran into a problem, someone told me that it was because my ply might be invalid and it was, but how do I fix it?

I have ply = FindMetaTable(“Player”) at the top of the file

and the function is:


function ply:ShortSteamID()
	--CheckValid(ply)
	local id = self:SteamID()
	local id = tostring(id)
	local id = string.Replace(id, "STEAM_0:0:", "")
	local id = string.Replace(id, "STEAM_1:1:", "")
	local id = string.Replace(id, "STEAM_0:1:", "")
	local id = string.Replace(id, "STEAM_1:0:", "")
	return id
end

How do I fix a class being invalid?

Where your CheckValid is, put


if not IsValid(ply) then return end

You can return an empty string too if you need a string to be returned for other functions.

Thing is I need it to give me the correct I’d since its going to be used in the database

STEAM_0: would be the most you can snip because the second 0/1 can change… BUT, storing a full steamid isn’t an issue in a normalized or even partially normalized database structure ( where you set up say an accounts table that stores steamid, etc about the player and assigns an Auto-incrementing / primary number to that row automatically and then simply reference that user using the account id instead of steamid everywhere else in the database )…

If you’re storing the steamid everywhere then it can be slimmed down…

Example of my structure:


CREATE TABLE IF NOT EXISTS `accc_user_accounts` (
  `userid` bigint(20) NOT NULL AUTO_INCREMENT,
  `steamid` varchar(50) NOT NULL,
  `steamid64` bigint(20) NOT NULL,
  `steamuid` varchar(20) NOT NULL COMMENT 'New SteamID [U...]',
  `uniqueid` bigint(20) NOT NULL,
  `username` varchar(50) NOT NULL,
  `user_connects` bigint(50) DEFAULT NULL,
  `user_total_playtime` bigint(50) NOT NULL DEFAULT '0',
  PRIMARY KEY (`userid`),
  UNIQUE KEY `steamid` (`steamid`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=121 ;

And data:


INSERT INTO `accc_user_accounts` (`userid`, `steamid`, `steamid64`, `steamuid`, `uniqueid`, `username`) VALUES
(-1, 'RCON', -1, 'RCON', -1, 'RCON'),
(0, 'BOT', -1, 'BOT', -1, 'Bot'),
( 1, 'STEAM_0:1:4173055', 76561197968611839, '[U:1:8346111]', 3175329094, 'Acecool' );

And everywhere else, instead of using my full steamid, I simply reference 1 ( the auto-incremented primary key to my account ).

Think of it as a table.insert, starting at 1 ( the 0 and -1 accounts were put there manually ) and when the player connects you fetch the key so you can reference the account. Then you use that key for other tables to fetch / update / insert / delete information. It is a lot shorter than storing the steamid over and over and over and you don’t need to modify the string which might be unreliable ( you removed too much ).

So INstead of using the steam Id as reference youre storing it once and then checking if its there and using that rows primary key as reference?

Exactly. When the player connects I use my networking system to store a private flag on the player called account_id; using that I can query the characters table to get the account characters. Then, when the player selects a character, I can store it under character_id and manage inventory and everything else related to the character without needing to reference steamid more than once ( on connect ).

But Youve managed to get their steam IDMine’s erroring when I try referencing ply


function ply:checkDatabase()
	local q = db:query("SELECT * FROM wojserve_GmodServer1.WojBuildServer WHERE EXISTS (SELECT * From WojBuildServer WHERE SteamID = " .. self:SteamID() .. ")")
	if  q == true then
		print(data)
		print("User Exists In Database")
	elseif q == false then
		print("User Does Not Exist In Database")
	else
		print("query Errored")
	end
end

this checks if the steamID is in the database but throws a null entity error on local q line

[editline]22nd November 2014[/editline]

I can use self:SteamID() in playerSpawned() in init.lua without a problem

Read up on database normalization. There are also query types called pivots which basically lets you link x number of tables together ( using standard joins ) and create faux column-names out of row data… So instead of just using joins to link data together and returning say x number of rows with identical data except two columns ( key and data ) you can turn the key into a fake column name and the data into the value of the column name so you only get 1 row returned instead of 20 or whatever.

I made a prepared statement generator for generating SQL ( mainly for pivots as those queries can be 30 lines long or longer; but all data that goes IN gets SQLStr’d automatically ): https://dl.dropboxusercontent.com/u/26074909/tutoring/database/sql_query_builder_example_1.lua.html


Also, make sure you SQLStr the SteamID…

So… WHERE SteamID=" … SQLStr( self:SteamID( ) ) … "

which will generate: WHERE SteamID=“STEAM_0:1:4173055”

with the quotes. Never trust user-input into databases, always escape the data otherwise you’re risking injection exploits, complete database delete, etc…

Additionally, instead of q == true and q == false, I’d recommend doing:
if ( q ) then … else … end

or

if ( !q ) then … else … end

etc…

Ok so I could do it that way but what if this happens on another thing, how do I fix ply being invalid? I have ply = FindMetaTable(“Player”) at the top.

Adding things to a meta-table is different than checking to see if a player is valid.

You’re using ply as the player-meta table reference variable ( whereby ply, pl, _p, etc… are commonly used to reference the player object ).

So, the meta-table is basically a template for the object. By adding functions to the template, you allow the object to access them.

For example, if you add a function to the player meta-table called Blah, then you could do something like this:


hook.Add( "PlayerSpawn", "BlahTest", function( _p )
	_p:Blah( );
end );

So, _p is just the argument for PlayerSpawn which references the object ( which is a copy of the template onto the “player” ), but it could’ve just as easily been named ply, pl, or whatever…

So, instead of using ply as the meta-table reference var, I suggest doing something slightly different. Either call it plyMeta, or something unique. I use META_PLAYER ( CONSTs naming where everything is uppercase, never-changing [ other languages have const modifier which prevents data from being modified after initialization ] and global [ so other scripts in the same realm can use it ] ) which is an easy to remember var name.


META_PLAYER = FindMetaTable( "Player" );
function META_PLAYER:Blah( )
	if ( !IsValid( _p ) ) then return; end
	print( _p:SteamID( ) );
end

hook.Add( "PlayerSpawn", "BlahTest", function( _p )
	_p:Blah( ); -- will now print the players steamid on spawn, if valid... otherwise it'll print nothing.
end );

Where are you accessing the player object ( it may be why it is nil ). Show us more code / context…

The only reason Im accesing the player meta is so I can get the player that just connected steam id.

[editline]22nd November 2014[/editline]

Ok I’ve managed to fix the ply problem but Im still getting a query errored from this function


function ply:checkDatabase()
	local q = db:query("SELECT * FROM wojserve_GmodServer1.WojBuildServer WHERE EXISTS (SELECT * From WojBuildServer " .. SQLStr(self:SteamID()) .. ")")
	if  q == true then
		print(data)
		print("User Exists In Database")
	elseif q == false then
		print("User Does Not Exist In Database")
	else
		print("query Errored")
	end
end

in intit.lua in playerSpawn()


connectDatabase()
  databaseWait()
  ply:checkDatabase()

The query should return false causing it to print The user does not exist in the database but instead it is not returning anything

[editline]22nd November 2014[/editline]

Ok I’ve managed to fix the ply problem but Im still getting a query errored from this function


function ply:checkDatabase()
	local q = db:query("SELECT * FROM wojserve_GmodServer1.WojBuildServer WHERE EXISTS (SELECT * From WojBuildServer " .. SQLStr(self:SteamID()) .. ")")
	if  q == true then
		print(data)
		print("User Exists In Database")
	elseif q == false then
		print("User Does Not Exist In Database")
	else
		print("query Errored")
	end
end

in intit.lua in playerSpawn()


connectDatabase()
  databaseWait()
  ply:checkDatabase()

The query should return false causing it to print The user does not exist in the database but instead it is not returning anything

You don’t need to, the function already exists as _p:SteamID( )

If you’re using CheckPassword hook, then the player object isn’t valid yet, but the arguments that are passed in contain SteamID ( 64, I think, but you can simply use local _steamid = util.SteamIDFrom64( _steamid64 ); if a 64 bit steam id was used )…

Look at how I convert steamids:

If you use player_connect game-event, then SteamID32 is already passed in.

Here are some examples: https://dl.dropboxusercontent.com/u/26074909/tutoring/_redistributable/acecooldev_base/documentation/game_events/game_event_listeners.lua.html

[editline]22nd November 2014[/editline]

You didn’t add SteamID = SQLStr, you added SQLStr and removed SteamID =… By just adding the steamid in quotes into the query will produce an error.

[editline]22nd November 2014[/editline]

Also, look into callbacks. I don’t recommend forcing the server to wait for a query to finish executing ( will cause stuttering and prevent logic from being executed )…

Instead, do something else such as letting the player connect as usual, running the query and having the query run the normal callback where the callback then controls the NEXT stage of logic.

Here is an example: https://bitbucket.org/Acecool/acecooldev_base/src/master/gamemode/addons/_acecool_admin_system/modules/class_steam_api_sv.lua?at=master

In there I have 2 callbacks so I can simplify calls to:


// PlayerFamilySharing; NO ERROR CALLBACK. Returns true if sharing, and lenderid becomes SteamID of lender
		// but if not sharing, _lenderid is steamid of local player
		steam:PlayerFamilySharing( "STEAM_0:1:4173055", 4000, function( _bSharing, _lenderid )
			print( _bSharing, _lenderid );
		end );

So, if we look at PlayerFamilySharing function, you’ll see that first it sets up vars to use, then calls FetchData callback ( which handles grabbing raw data so I didn’t need to repeat http and a lot of the same stuff in each query ), then the PlayerFamilySharing function handles processing the data into nice arguments, then the callback is called which is shown above which has 2 easy to use arguments…

Callbacks are useful… Since MySQLOO / TMySQL operates in its own thread ( or executes asynchronously ) it won’t interrupt other processes currently running unless specifically told to do so. By passing the work off to a callback, you can finish other things while the query is running and when the query completes you can set it up to do other things ( such as spawn the player when you determine whether or not the player is in the system or not, or tell the client to open a vgui panel to create the character via networking, etc… )

I was searching for a callback resource but I couldn’t find any good ones, if you know any good tutorials please link them, thanks for all your help I’ll revise the database system using this Information especially the callbacks thing because we would not want that slowing up the server.

A callback is basically a function that a function calls. Think of the console command system…

concommand.Add( command, callback ), hook.Add( id, name, callback ), etc…

So, your database setup already does it. Your OnSuccess / OnFailure methods are callbacks which are called when the query completes or fails. Basically you would simply call a function inside your query to do something when that query has completed to proceed to the next phase, or set a variable / team or whatever so anything running in the background will proceed…

You just need to think non-linearly or linearly depending on how you look at it. Example: Linear in terms of having the player join, set them to spectator and execute query when they join.

Non-linear in terms of breaking the chain at that point ( BUT, while not using database wait, meaning you’re allowing think hooks, etc to run while waiting on the next instruction / next phase of player loading ). Linear in terms of continuing the chain from inside the database callback… So, if they’re not in the system, create. If they are, then load. After create or load in the same function, you’d set up the values then switch their team and spawn them.

That would be one example. Does that make more sense?

So basically I setup a function for each step in the database process such as checking if the player exists one function which then calls a function depending on the outcome right ?, this basically will allow the server to run logic between the functions being called.

It is entirely up to you. What I mean by using callbacks is you should have a flow for your server to follow depending on the plans of how client loading should occur…

If you want the database to always ensure there is an account row for the player, you may consider using player_connect game-event or ChestPassword hook to check / setup the row.

When the player fully connects, you should already have the basic information stored somewhere to avoid needing a query ( or you can query it then, it doesn’t matter ).

You would need to make sure your code executes in a certain order. You can do it by calling hooks after certain events, functions / callbacks, etc…

Here’s a few examples of how I used to do it ( this would’ve been called on player_connect ), basically create the account if it doesn’t exist, or if it exists and first join time wasn’t set ( default 0 in database ) then it updates it.

ugly code…


function DB.InitialAccountCheckOrCreate( steamid, name )
	-- local _query = "SELECT user_connects, firstlogin FROM " .. DATABASE_USER_ACCOUNTS_TABLE .. " WHERE steamid='" .. DB.Escape( steamid, true ) .. "' LIMIT 1";
	local _query = DB.SelectQuery( DATABASE_USER_ACCOUNTS_TABLE, { "user_connects", "firstlogin" }, DB.GenerateString( true, "steamid", steamid ), 1 );
	DATABASE:Query( _query, function( row )
		if ( #row > 0 ) then
			local _first = DB.GetValue( row, 1, 2, "firstlogin" );
			if ( tonumber( _first ) == 0 ) then
				DATABASE:Query( "UPDATE " .. DATABASE_USER_ACCOUNTS_TABLE .. " SET firstlogin='" .. DB.Escape( os.time( ), true ) .. "' WHERE steamid='" .. DB.Escape( steamid, true ) .. "' LIMIT 1" );
			end
		else
			MsgC( COLOR_ORANGE, "[" .. GAMEMODE.Name .. "] User \"" .. DB.Escape( name, true ) .. "\" [ " .. DB.Escape( steamid, true ) .. " ] doesn't exist - INSERTING.
" );
			DATABASE:Query( "INSERT INTO " .. DATABASE_USER_ACCOUNTS_TABLE .. " SET steamid='" .. DB.Escape( steamid, true ) .. "', username='" .. DB.Escape( name, true ) .. "', accesslevel='1', firstlogin='" .. DB.Escape( os.time( ), true ) .. "', user_connects='1'" );
		end
	end, QUERY_FLAG_ASSOC );
end

This can be optimized using ONE query with logic like you’re doing.

I do something different now where it’ll create the account if it doesn’t exist, or update connects if it does on player_connect.

When the player has fully connected to the server, they’re locked in as spectator ( because of a character system ), and it fetches their account information which also includes character info. The client receives a message to open the character selection / creation panel. Based on the client choices it’ll load the rest of the character information, or open up a panel to create a new character ( when done they’re returned to character selection so they can select and load in ). So, when they select their char, it loads them into the game.

All of this is easily done because my game-mode is coded to support certain things… If the client isn’t loaded into the game, nothing happens / executes. If they’re loaded but haven’t selected a char, nothing happens. Once they select a char, and are spawned in, the data flows…

How you set up your system depends on how you want it to flow. If you know the sequence at which things should happen, it won’t be hard to turn it into code ( which is why developers always start in a “design” phase where they write down how logic flows, or what should happen and when it should happen ).

I’d suggest, figure out the flow of how you want things to happen and when. There are a ton of hooks to tie into to ensure the logic can be followed. Remember to prevent things from happening until events happen. You can make helper functions to prevent things from running in think / tick / logic / draw / render hooks until you’re at the stage you want to be at. You could store it all in a single var using enumeration.

If you give us an idea of exactly what you want, we can give you a list of hooks to look into or what else needs to be done.

Oh ok I get it, Guess Il be planning and reading update on hooks tomorrow. Thx for all your help