Game Development Community

Asynch login and team assignment (semi-solution)

by Stephen Zepp · in RTS Starter Kit · 12/24/2004 (2:33 am) · 3 replies

One of the assumptions in the RTS-SK (and it's a good one, don't get me wrong!) is that all players are going to be logging in to the actual game (from the lobby) at once, and once the game has started, no "new" players will be logging in to join the game. This is standard for pretty much any multi-player RTS, and works fine in those implementations.

However, at least a couple of projects out there have been either asking directly, or implying that they will have some form of a persistent environment (exists beyond/after the scope of a normal "RTS game"), and team assignment can be an issue here, since the stock RTS-SK scripts base initial team assignment on the order in which a player's client object is in the set ClientGroup.

NOTE: This assumes that you have already removed/modified the RTS Lobby scripts, and have either a dedicated server up and running, or a "permanent" multiplayer hosted game in your environment. The discussion doesn't cover those steps.

In /server/scripts/core/gameConnection.cs:

// Add to team
   %clientIndex = %this.getClientIndex();
   %this.setTeam(%clientIndex);

and

function RTSConnection::getClientIndex(%this)
{
   // Find our index in the client group...
   for(%i=0; %i<ClientGroup.getCount(); %i++)
      if(ClientGroup.getObject(%i) == %this)
         return %i;
         
   return -1;
}

If you take a look at possible states using the above implementation, you'll see that you can run into this type of case:

Player A logs in as the first player, gets added to ClientGroup as index 0, and therefore gets assigned to team 0.

Player B logs in as second player, gets team 1.

Player A logs out.

--Player B is still "team 1" since the team assignment only happens on initial onClientEnterGame
--however, Player A's client object has now been removed from ClientGroup, therefore Player B's object is now the only one in the group, and therefore is now index 0.

Player C logs in, gets added to the ClientGroup as index 1, and therefore gets assigned to...team 1, which is Player B's team.

(cont).

#1
12/24/2004 (2:42 am)
Now, team assignment in this type of multiplayer game is almost completely project specific, so there really isn't a "one solution fits all" implementation--you're going to have to evaluate your game mechanics rules for how your players should be assigned teams (and I assume that it will probably be persistent across game sessions, etc.).

As part of our milestone 1 for our project, we needed to fix the "gets assigned a used team" issue, but weren't ready for implementing our full game mechanics for team assignment and persistence, so we came up with a "mostly works" interim solution. This solution works has two assumptions:

1) Teams should be balanced, and the only time a player should be added to an existing team is if all of the other teams either have the same number of members, or 1 higher (if Team 4 has 2 players, and all the rest of the teams have 1, then the rest of the teams have a chance of getting the "new" player).

2) This is an interim solution, and needs work! Specifically, the team rotation stuff doesn't seem to catch all cases, but it should help others in the same scenario come up with a more robust solution.

Ok, here goes.

In /server/scripts/core/gameConnection.cs, you'll want to make the following additions/changes:

(cont)
#2
12/24/2004 (2:48 am)
In /server/scripts/gameConnection.cs, at the top is fine (mine is just above the old getClientIndex() function):

// SRZ: tier 1 hack for setting a client's team on entering game. This should 
// be replaced in the persistent store upgrade for your project

$Server::TeamInfo::TeamCount[0] = 0;
$Server::TeamInfo::TeamCount[1] = 0;
$Server::TeamInfo::TeamCount[2] = 0;
$Server::TeamInfo::TeamCount[3] = 0;
$Server::TeamInfo::TeamCount[4] = 0;
$Server::TeamInfo::TeamCount[5] = 0;
$Server::TeamInfo::TeamCount[6] = 0;
$Server::TeamInfo::TeamCount[7] = 0;
$Server::TeamInfo::MaxTeams = 8;
$Server::TeamInfo::CurrentClientsPerTeam = 1;


function RTSConnection::getInitialTeam(%this)
{
  %validTeamFound = "false";
  
  // I hate initialization loops, but this is a temp fix to a tier 1 imp in the 
  // first place, so here we go!
  %curClientsPerTeam = 0;
  for (%index=0;%index < $Server::TeamInfo::MaxTeams; %index++)
  {
    if ($Server::TeamInfo::TeamCount[%index] > %curClientsPerTeam )
    {
      %curClientsPerTeam = $Server::TeamInfo::TeamCount[%index];
    }
  }
  %randomTeam = mFloor( getRandom( $Server::TeamInfo::MaxTeams ) );
  for ( %attemptCount = 0; 
        %attemptCount < ($Server::TeamInfo::MaxTeams * 4); 
        %attemptCount++) 
  {    
  // we should cycle here through 0 to $..MaxTeams, offset by the initial randomTeam.
  // if we don't find a match in the first cycle of attempts (remember, it can 
  // wrap around) then we assume each team has at least 
  // ($ ..CurrentClientsPerTeam, so we need to increment that counter and 
  // try again. Try a max of 4 times (see loop check above) for now.

    if ($Server::TeamInfo::TeamCount[(%randomTeam + %attemptCount) % 
        $Server::TeamInfo::MaxTeams] <  %curClientsPerTeam)
    {
      %validTeamFound = "true";
      break;
    }
    // We didn't find any in this pass, so we must be even, or need counter increment
    if ( ( (%attemptCount % $Server::TeamInfo::MaxTeams) == 0 ) &&
         ( %attemptCount > 0 )  )
    {
      %curClientsPerTeam++;
    }
  }
  // at this point we should have a valid team.
  if (%validTeamFound $= "false")
  {
    echo("RTSConnection::getInitialTeam()--couldn't find a valid team, this isn't good!");
    return -1;
  }
  return ( (%randomTeam+ %attemptCount) % $Server::TeamInfo::MaxTeams);
}

in the same file, onClientEnterGame(), replace
// Add to team
   %clientIndex = %this.getClientIndex();
   %this.setTeam(%clientIndex);

with

// Add to team
   %clientIndex = %this.getInitialTeam();
   if (%clientIndex < 0)
   {
     //not a good thing
     %clientIndex = 0;
   }
   %this.setTeam(%clientIndex);
   $Server::TeamInfo::TeamCount[%clientIndex]++;

and in onClientLeaveGame(), at the bottom add:
$Server::TeamInfo::TeamCount[%this.getTeam()]--;

As I mentioned, this is an interim solution for us, and meets our needs. However, it actually does not work well in balancing the teams when you start getting enough players that they get assigned to existing teams--for us, we just wanted to be able to allow our testers to use our dedicated server, so all we really cared about getting to work was the random assignment without overlaps. If you really do want balanced teams when you have more players than teams, this code will need fixing.

Side Note: After implementing most of this, I did find out that there is already a server variable in server defaults.cs called $Pref::Server::maxTeams. Personally, I didn't want this stuff exported into prefs.cs, so I kept my own variable names, but it does now require you to update both variables (defaults.cs as well as gameConnection.cs) unless you make the variables named the same.
#3
12/25/2004 (4:11 pm)
I love you man!