Need help understanding gamemode inner workings.

I am having difficulty with how round-based gamemodes work. I do have some c++ experience, therefore reading LUA code is not that hard, but I feel like missing the understanding the anatomy of a game mode. It appears that the fretta code has this already set, but I would like actually understand how it works. What “starts” the round and what ends it.

Generally, you decide whether a round should start in the GM:Think() function, i.e. all players dead, start a new round. There is no predefined ‘start round’ function outside of Fretta, so you can add one as ‘GM:StartRound()’ for example, and then call that from GM:Think().

How much can think “take”? I mean doing alive player checks every frame is within reasonability? Asking because I am not sure how many things per frame can be going before something drags the game down.

Maybe you could use the Tick hook, or simply place a delay inbetween each test if you like.

Don’t do it in think, do it in OnPlayerDeath or whatever.
Whenever a player dies check if there’s only one player left (if it’s a last man standing type gamemode).

Example…
[lua]function GM:PostPlayerDeath( ply )

self.BaseClass:PostPlayerDeath( ply )

// If freeforall, check if there's only one player left alive, if there is then they're the winner
if( self.TeamBased || !self:InRound() ) then return end

local AlivePlayers = self:GetAlivePlayers()
if( #AlivePlayers == 1 ) then 
	self:RoundEndWithResult( AlivePlayers[1] )
elseif ( #AlivePlayers == 0 ) then
	self:RoundEnd()
end

end[/lua]

If teamplay is enabled the round ending is handled by the base Fretta code so we don’t need to do anything. Otherwise it ends the round if there’s only one player left.

GetAlivePlayers() is just a simple function which returns a table of alive players.

I am not using fretta, as I want to get my own custom gamemode created. It is going to be something round-based with forced team placement (players are randomly put in teams each round). I feel using so much pre-made fretta stuff would be more difficult to customize than doing it from scratch.
If that is adumb decision on my side, let me know.

Nah that’s cool.
You’ll probably learn how things work better if you code it yourself anyway.

But I’d still do your ‘has round ended’ checks whenever a player dies.
That’s in addition to setting a timer when the round starts which will call a function if noone wins in a certain time limit.

GetAlivePlayers() is just a simple function which returns a table of alive players.

Are tables “arrays”? Looks as if they are. I guess this function would be useful to place players in the different teams. Still planning how to best do it.
The idea I have so far is:

  1. New round starts
  2. Set all players to spectators
  3. Force all players into the different teams

Pre game period (lasts 20 seconds)
4. All players spawn in their teams.

In game period
5. If one team is compltely defeated - end the game, and jump to end-game

end-game period
6. Show scores an other info

  1. Start a completely new round

I think 2. Can be skipped entirely, as when the new roudn starts, players can instantly spawn in their new teams.

btw.
How does the game determine what if a player is “alive”? If I create my spectator team, and who joins it is placed in OBservs mode 5, are the players in there actually “dead”? Or just in a team of observing people? I mean how “coded” are dead and alive states?

I believe a live player is one that is spawned and has over 0 hp.
To get all players regardless of their status use player.GetAll

It will return an array, or table, of all players that are currently ingame (not the ones connecting).

Then you can loop through that table and do stuff to them individually.

[lua]for k,v in pairs(player.GetAll()) do
print(v:Nick())
end[/lua]
That example prints the name of every player on the server.

[lua]function GM:GetAlivePlayers()

local AlivePlayers = {}

for _,pl in pairs( player.GetAll() ) do
	if( pl:Alive() && !pl:IsSpectating() ) then
		table.insert( AlivePlayers, pl )
	end
end

return AlivePlayers

end
[/lua]

And pl:IsSpectating is a simple function added to the player metatable…
[lua]function meta:IsSpectating()
return ( self:GetObserverMode() != OBS_MODE_NONE )
end
[/lua]

Non-spectators should always be OBS_MODE_NONE as far as I’m aware, so that’s how I’m doing it.

Wow, these LUA for loops are weird to look at if you are used to c++.
Anyway, I created a console command to list all players on the server, and counts their total number.

function Listplayers()
local totalplayercount = 0
for k,v in pairs(player.GetAll()) do

Msg("Key: "..k.. " Name: ".. v:Nick() .. "

")
totalplayercount = totalplayercount + 1

end 
Msg("Total players on server present: "..totalplayercount.."

")
end
concommand.Add( “snip_listplayers”, Listplayers )

Works, but I have some questions about this:

  1. When one player disconnected and joined, he took up his old position in the list. I expected the list to rearrange?
  2. Why does the game fail to spawn bots (which I want to use to test this function without real players present?

totalplayercount = totalplayercount + 1
is there no extra “local” needed in fron of this? Because it was declared local in the same function before that, correct?

Well that’s the for in. In a case where you don’t care about the key you can also use this :

[lua]local PlayerList = player.GetAll()
for i = 1, #PlayerList do
print( PlayerList*:Nick() )
end[/lua]

being the lenght operator.

You can also use while and until loops, and maybe another I’m forgetting, but a for loop is what you want to use for looping trough a table.

Added more to the function.
Basically it lists all players on the server, then selects one random and puts him in another team.


function AssignTeams()

//List all players and count them
//local playerlist={}
local  playerlist = player.GetAll()
local totalplayercount = 0
	for k,v in pairs(player.GetAll()) do  
        
	Msg("Key: "..k.. " Name: ".. v:Nick() .. "

")
totalplayercount = totalplayercount + 1

    end 
Msg("Total players on server present: "..totalplayercount.."

“)
Msg(playerlist[1])
Msg(”
")

//Select a random player from the list and place him in team 1
Msg("Selecting a random player....")
Msg("

")
local rd = math.random(totalplayercount)
Msg("Selected player: “)
Msg(playerlist[rd])
Msg(”
“)
Msg(“moving selected player to Team 1…”)
Msg(”
")
playerlist[rd]:SetTeam( 1 )

end


I have more questions:

  1. What are the rules for placing all the functions in different files? This function is in shared.lua, which is created on both client and server, correct? If I wwant to divide functions into different files, what controls which lua file is “used” on shared or client/server? Let’s say I want to put all Team assignment things into a separate lua file, what controls if it can be used by the right “entity”? (Client/Server)

if you include a file in cl_init.lua it is used on the client, if it’s included in init.lua its used on the server. if it’s included on both it runs on both, but not every function works on both so watch out for that.

Like a think function is shared, but if your setting a serveside variable in the shared think function it will work but also output errors on the client, which is where if( CLIENT ) then comes in handy or if( SERVER ) then

So if I want to create a console command that simply displays some text, like version info of the gamemode, it is safe to put that in a lua file that only is included to the client?

About the difference between include and download.

Download = file is sent to client
Include = If it is included on the server, they client will not need that file?
Example: The Player.lua is only included on the init.lua. This correct?

Include will pretty much run a script on either the client or server, depending on where you use it.

AddCSLuaFile should automatically send the file to the client and run it.