Need help tweaking a "gun-game" style rank-up system.

Okay, so this is my first gamemode project, so if I missed something obvious here please do not hesitate to point that out.
The game just uses the default HL2 weapons, and I’ve gotten the weapon tiers working and everything works fine until I kill a player. Instead of stripping the current weapon, switching the player’s class to the next weapon tier, and giving them that weapon, the player is immediately given all the weapons up to the final tier, #8.

Obviously the code did not pause between the if functions, and continued with the parameters all the way down the line because as it checks if the person is a class, it moves them up, checks for that class and so on…

My guess is that I need to separate the if functions somehow, but I’m not quite sure how to do that. Any ideas?

Here’s my code:
[lua]function GM:PlayerInitialSpawn( ply )
ply:SetTeam( 1 )
player_manager.SetPlayerClass( ply, “T1” )
end

// What happens when someone is killed:
function GM:PlayerDeath( victim, cause, attacker )
if attacker:IsPlayer() then
attacker:StripWeapons()
else
end

// Rank-Up System
if player_manager.GetPlayerClass( attacker, "T1" ) then
	player_manager.SetPlayerClass( attacker, "T2" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T2" ) then
	player_manager.SetPlayerClass( attacker, "T3" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T3" ) then
	player_manager.SetPlayerClass( attacker, "T4" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T4" ) then
	player_manager.SetPlayerClass( attacker, "T5" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T5" ) then
	player_manager.SetPlayerClass( attacker, "T6" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T6" ) then
	player_manager.SetPlayerClass( attacker, "T7" )
	player_manager.RunClass( attacker, "Weapon" )
else
end

if player_manager.GetPlayerClass( attacker, "T8" ) then
// TO DO: work out the winner here
end

// Steal Freeman Status System
if player_manager.GetPlayerClass( victim, "T8" ) then
	player_manager.SetPlayerClass( victim, "T7" )
	player_manager.SetPlayerClass( attacker, "T8" )
	MsgN( HUD_PRINTTALK, "Dethroned! " ..attacker:Nick().. " is the new Freeman!" )
else
end

end[/lua]

If you really wanna keep all the if statements then just reverse the order of testing. However a better way of doing this would be to use a table to advance.

[lua]local classes =
{
“T1”, “T2”, “T3”
}

function GM:PlayerDeath(victim, cause, attacker)

attacker.Level = attacker.Level or 1

local nextLevel = attacker.Level + 1

if nextLevel >= #classes then
	-- Attacker wins
else
	player_manager.SetPlayerClass(attacker, classes[nextLevel])
	attacker.Level = nextLevel
end

end[/lua]

I haven’t tested this, just an example.

I wrote quite a few iterations of this type of system including ones that use Frags as score, or it’s own counter, and with a lot of options. Hopefully these help out:

https://dl.dropboxusercontent.com/u/26074909/tutoring/_gamemode_logic/gun_race/sv_gun_race.lua.html

https://dl.dropboxusercontent.com/u/26074909/tutoring/_gamemode_logic/gun_race/sv_upgrade_weapons_for_kills.lua.html

The reason you are incrementing to the top right away is because each individual if statement gets executed one after the other. You can either reverse the order so highest is first, lowest last ( typical situation even with if / elseif because you typically have an upper bounds but not a lower bounds so this can simplify logic from if x > y and t < u to x > y…

You could also just add else in from of the if, no spaces… Also, get rid of your else / end for each of them. If you don’t use else you don’t need to add it.

You can also simplify your logic quite a bit by making it dynamic. Use a table… so local _upgrades = { T1 = “T2”, T2 = “T3”, T3 = “T4”, T5 = “T6”, T6 = “T7”, T7 = “T8” }; then simply do: local _next = _upgrades[ _p:GetPlayerClass( ) ]; if _next then … end

EDIT: 2 methods, 1 fixes yours and describes another problem. The other makes it simple:

“Fixed”


function GM:PlayerInitialSpawn( ply )
	ply:SetTeam( 1 )
	player_manager.SetPlayerClass( ply, "T1" )
end

// What happens when someone is killed:
function GM:PlayerDeath( victim, cause, attacker )
	if attacker:IsPlayer() then
		attacker:StripWeapons()
	end

	// Rank-Up System
	if player_manager.GetPlayerClass( attacker, "T1" ) then
		player_manager.SetPlayerClass( attacker, "T2" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T2" ) then
		player_manager.SetPlayerClass( attacker, "T3" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T3" ) then
		player_manager.SetPlayerClass( attacker, "T4" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T4" ) then
		player_manager.SetPlayerClass( attacker, "T5" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T5" ) then
		player_manager.SetPlayerClass( attacker, "T6" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T6" ) then
		player_manager.SetPlayerClass( attacker, "T7" )
		player_manager.RunClass( attacker, "Weapon" )
	elseif player_manager.GetPlayerClass( attacker, "T8" ) then
	// TO DO: work out the winner here
	end

	// Steal Freeman Status System
	if player_manager.GetPlayerClass( victim, "T8" ) then
		player_manager.SetPlayerClass( victim, "T7" )
		-- player_manager.SetPlayerClass( attacker, "T8" ) -- This is already done above...
		MsgN( HUD_PRINTTALK, "Dethroned! " ..attacker:Nick().. " is the new Freeman!" ) -- use PrintMessage
	end
end

Simplified



local UpgradesTable = {
	T1 = "T2";
	T2 = "T3",
	T3 = "T4",
	T4 = "T5";
	T5 = "T6";
	T6 = "T7";
	T7 = "T8";
};
local StartRank = "T1";
local DethroneRank = "T7";
local MaxUpgrade = "T8";


function GM:PlayerInitialSpawn( _p )
	_p:SetTeam( 1 );
	player_manager.SetPlayerClass( _p, StartRank );
end

// What happens when someone is killed:
function GM:PlayerDeath( _p, _w, _attacker )
	if ( !IsValid( __attacker ) ) then return; end
	if _attacker:IsPlayer( ) then
		_attacker:StripWeapons( );
	end

	// Get the class id and see if there's an upgrade...
	local _class = _attacker:GetClassID( ) || StartRank;
	local _next = UpgradesTable[ _class ];

	// Rank-Up System
	if ( _next ) then
		player_manager.SetPlayerClass( _attacker, _next );
		player_manager.RunClass( _attacker, "Weapon" );
	else
		// TO DO: work out the winner here
	end

	// Dethrone...
	if ( _p:GetClassID( ) == MaxUpgrade ) then
		player_manager.SetPlayerClass( _p, DethroneRank );
		PrintMessage( HUD_PRINTTALK, "Dethroned! " .. _attacker:Nick( ) .. " is the new Freeman!" );
	end
end

Try not to hard-code a lot of things. It makes the code read better if you have “settings” or certain vars at the top, and don’t need to update 100 places when you make a simple change.

Edit, and one more…

Dynamic
Simplified



local RankPrefix = "T";
local StartRank = 1;
local MaxRank = 8;

local UpgradesTable = { };
for i = StartRank, ( MaxRank - 1 ) do
	UpgradesTable[ RankPrefix .. i ] = ( RankPrefix .. ( i + 1 ) );
end

function GM:PlayerInitialSpawn( _p )
	_p:SetTeam( 1 );
	player_manager.SetPlayerClass( _p, RankPrefix .. StartRank );
end

// What happens when someone is killed:
function GM:PlayerDeath( _p, _w, _attacker )
	if ( !IsValid( __attacker ) ) then return; end
	if _attacker:IsPlayer( ) then
		_attacker:StripWeapons( );
	end

	// Get the class id and see if there's an upgrade...
	local _class = RankPrefix .. ( _attacker:GetClassID( ) || StartRank );
	local _next = UpgradesTable[ _class ];

	// Rank-Up System
	if ( _next ) then
		player_manager.SetPlayerClass( _attacker, _next );
		player_manager.RunClass( _attacker, "Weapon" );
	else
		// TO DO: work out the winner here
	end

	// Dethrone...
	if ( _p:GetClassID( ) == MaxUpgrade ) then
		player_manager.SetPlayerClass( _p, RankPrefix .. ( MaxRank - 1 ) );
		PrintMessage( HUD_PRINTTALK, "Dethroned! " .. _attacker:Nick( ) .. " is the new Freeman!" );
	end
end

Absolutely marvelous, sir.
I had thought about making a math system to just add 1 to the player’s current class, like your simple/dynamic example, but I didn’t know about prefixes and didn’t want to break what I already had.

The idea to reverse the order never occurred to me, I feel silly for not realising it.

Once again marvelous help, thank you! :smiley:

Bad news, it gets my character from tier 1 to tier2, but after that it just keeps giving me tier 2 for every kill.

Thats what you get from copy pasting code, it seems that the code from Acecool has a flaw where you wont be able to step up ever.

EDIT: Just looked at the code again, so much about readability. The example from him is making things rather complicated. You best stick to your own code and try to get better at it.

It should work. Basically it pulls the current class id ( which is what player_manager uses for the class name ) from the table KEY and returns the value. So T1 returns T2, T2 returns T3, and so on.

The only time the key shouldn’t exist, is if it is at T8 which is where the “winner” code goes and where someone gets dethroned in the next step…

String in left is key, string on right is value


local UpgradesTable = {
	T1 = "T2";
	T2 = "T3",
	T3 = "T4",
	T4 = "T5";
	T5 = "T6";
	T6 = "T7";
	T7 = "T8";
};
	

If you’re modifying a game-mode alter the GM:InitialPlayerSpawn to hook.Add( “PlayerInitialSpawn”, “unique name here”, function( _p ) … end );

With PlayerDeath, if altering a game-mode use hook.Add ( remove all returns with non-nil values and change the logic so it doesn’t prevent the GM:PlayerDeath function from running )…

Now, if it sticks on T1 then it is with the dynamic version, the non-dynamic version should work properly, because PlayerDeath code never gets executed:

The issue was because I ended up building the RankPrefix into the table so it isn’t needed anywhere else except on the variables that are stand-alone such as StartRank, etc… When we get the rank, it would’ve returned TTx because GetClassID returned Tx and we added the prefix on it one more time when we only needed it for StartRank… So the issue was when I replaced vars the first one got overwritten incorrectly…

So, change


if ( !IsValid( __attacker ) ) then return; end	

to


if ( !IsValid( _attacker ) ) then return; end	


local _class = RankPrefix .. ( _attacker:GetClassID( ) || StartRank );	

to


local _class = _attacker:GetClassID( ) || ( RankPrefix .. StartRank );	

and


if ( _p:GetClassID( ) == MaxUpgrade ) then	

to


if ( _p:GetClassID( ) == ( RankPrefix .. MaxRank ) ) then	

Some people often add small mistakes to code to see if you can debug it or correct it. The non-dynamic version should have no issues… These were my mistakes though.

An easy way to check code is by pasting it into Notepad++ and double-clicking a variable ( to highlight it ) and it’ll highlight all other copies of it in the code. If you don’t see it used anywhere then there is likely an issue.

Here’s the new dynamic version with the changes:


local RankPrefix = "T";
local StartRank = 1;
local MaxRank = 8;

local UpgradesTable = { };
for i = StartRank, ( MaxRank - 1 ) do
	UpgradesTable[ RankPrefix .. i ] = ( RankPrefix .. ( i + 1 ) );
end

function GM:PlayerInitialSpawn( _p )
	_p:SetTeam( 1 );
	player_manager.SetPlayerClass( _p, RankPrefix .. StartRank );
end

// What happens when someone is killed:
function GM:PlayerDeath( _p, _w, _attacker )
	if ( !IsValid( _attacker ) ) then return; end
	if _attacker:IsPlayer( ) then
		_attacker:StripWeapons( );
	end

	// Get the class id and see if there's an upgrade...
	local _class = _attacker:GetClassID( ) || ( RankPrefix .. StartRank );
	local _next = UpgradesTable[ _class ];

	// Rank-Up System
	if ( _next ) then
		player_manager.SetPlayerClass( _attacker, _next );
		player_manager.RunClass( _attacker, "Weapon" );
	else
		// TO DO: work out the winner here
	end

	// Dethrone...
	if ( _p:GetClassID( ) == ( RankPrefix .. MaxRank ) ) then
		player_manager.SetPlayerClass( _p, RankPrefix .. ( MaxRank - 1 ) );
		PrintMessage( HUD_PRINTTALK, "Dethroned! " .. _attacker:Nick( ) .. " is the new Freeman!" );
	end
end
	

Simplified ( had __attacker instead of _attacker )


local UpgradesTable = {
	T1 = "T2";
	T2 = "T3",
	T3 = "T4",
	T4 = "T5";
	T5 = "T6";
	T6 = "T7";
	T7 = "T8";
};
local StartRank = "T1";
local DethroneRank = "T7";
local MaxUpgrade = "T8";


function GM:PlayerInitialSpawn( _p )
	_p:SetTeam( 1 );
	player_manager.SetPlayerClass( _p, StartRank );
end

// What happens when someone is killed:
function GM:PlayerDeath( _p, _w, _attacker )
	if ( !IsValid( _attacker ) ) then return; end
	if _attacker:IsPlayer( ) then
		_attacker:StripWeapons( );
	end

	// Get the class id and see if there's an upgrade...
	local _class = _attacker:GetClassID( ) || StartRank;
	local _next = UpgradesTable[ _class ];

	// Rank-Up System
	if ( _next ) then
		player_manager.SetPlayerClass( _attacker, _next );
		player_manager.RunClass( _attacker, "Weapon" );
	else
		// TO DO: work out the winner here
	end

	// Dethrone...
	if ( _p:GetClassID( ) == MaxUpgrade ) then
		player_manager.SetPlayerClass( _p, DethroneRank );
		PrintMessage( HUD_PRINTTALK, "Dethroned! " .. _attacker:Nick( ) .. " is the new Freeman!" );
	end
end
	

It hurts that you would assume I have copied and pasted something I did not write. Of course I didn’t.

I also didn’t try the dynamic version, first I reversed the order of my long-winded (I believe you referred to it as hard-coded) rank system. The next thing I tried was turning the subsequent “if” statements into “esleif” statements. That’s how I got to the situation I am in presently.

tl;dr
Not just copying without trying to do work myself, just less competent/experienced. :stuck_out_tongue:

[editline]19th March 2015[/editline]

Damn, I noticed that almost immediately, thought it seemed off, but I just assumed that it was intended that way. Now I know not to ignore my gut feeling haha.

Once again, you’re one of the most helpful people I’ve encountered, very generous of you.

I’ll keep at it when I get back home today and have access to a computer with Notepad++.
Wish I was able to install it on school computers.

Never mentioned anything about hard-coded at all, and that you suddenly posted that its not working without a new sample you gave one the impression you took the code and it did not work at all, so sorry about that I guess.

Not gonna get started about why having a lookup for the next class is rather unreasonable.

Happy to say that I’ve gotten everything working now. Well, almost.

I have HUD_PRINTCENTER announce the winner at the end of the game, I just can’t get it to stay on the screen for long.

I’ve also been toiling for a way to disable the alt-fire for guns because if you get a kill while zoomed-in with the crossbow, you can’t zoom back out because now you’re holding a different weapon.