SLikeNet  0.1.3
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
TeamBalancer.cpp
Go to the documentation of this file.
1 /*
2  * Original work: Copyright (c) 2014, Oculus VR, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * RakNet License.txt file in the licenses directory of this source tree. An additional grant
7  * of patent rights can be found in the RakNet Patents.txt file in the same directory.
8  *
9  *
10  * Modified work: Copyright (c) 2017, SLikeSoft UG (haftungsbeschränkt)
11  *
12  * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
13  * license found in the license.txt file in the root directory of this source tree.
14  */
15 
17 #if _RAKNET_SUPPORT_TeamBalancer==1
18 
19 #include "slikenet/TeamBalancer.h"
20 #include "slikenet/BitStream.h"
22 #include "slikenet/peerinterface.h"
23 #include "slikenet/Rand.h"
24 
25 using namespace SLNet;
26 
27 enum TeamBalancerOperations
28 {
29  ID_STATUS_UPDATE_TO_NEW_HOST,
30  ID_CANCEL_TEAM_REQUEST,
31  ID_REQUEST_ANY_TEAM,
32  ID_REQUEST_SPECIFIC_TEAM
33 };
34 
36 
38 {
40  forceTeamsToBeEven=false;
41  lockTeams=false;
43 }
45 {
46 
47 }
48 void TeamBalancer::SetTeamSizeLimit(TeamId team, unsigned short limit)
49 {
50  teamLimits.Replace(limit,0,team,_FILE_AND_LINE_);
53 }
54 void TeamBalancer::SetDefaultAssignmentAlgorithm(DefaultAssigmentAlgorithm daa)
55 {
56  // Just update the default. Currently active teams are not affected.
58 }
59 void TeamBalancer::SetForceEvenTeams(bool force)
60 {
61  // Set flag to indicate that teams should be even.
62  forceTeamsToBeEven=force;
63 
64  // If teams are locked, just return.
65  if (lockTeams==true)
66  return;
67 
68  if (forceTeamsToBeEven==true)
69  {
70  // Run the even team algorithm
71  EvenTeams();
72  }
73 }
74 void TeamBalancer::SetLockTeams(bool lock)
75 {
76  if (lock==lockTeams)
77  return;
78 
79  // Set flag to indicate that teams can no longer be changed.
80  lockTeams=lock;
81 
82  // If lock is false, and teams were set to be forced as even, then run through the even team algorithm
83  if (lockTeams==false)
84  {
85  // Process even swaps
86  TeamId i,j;
87  for (i=0; i < teamMembers.Size(); i++)
88  {
89  if (teamMembers[i].requestedTeam!=UNASSIGNED_TEAM_ID)
90  {
91  for (j=i+1; j < teamMembers.Size(); j++)
92  {
93  if (teamMembers[j].requestedTeam==teamMembers[i].currentTeam &&
94  teamMembers[i].requestedTeam==teamMembers[j].currentTeam)
95  {
99  }
100  }
101  }
102  }
103 
104  if (forceTeamsToBeEven==true)
105  {
106  EvenTeams();
107  }
108  else
109  {
110  // Process requested team changes
111  // Process movement while not full
112  for (i=0; i < teamMembers.Size(); i++)
113  {
114  TeamId requestedTeam = teamMembers[i].requestedTeam;
115  if (requestedTeam!=UNASSIGNED_TEAM_ID)
116  {
117  if (teamMemberCounts[requestedTeam]<teamLimits[requestedTeam])
118  {
119  SwitchMemberTeam(i,requestedTeam);
121  }
122  }
123  }
124  }
125  }
126 }
127 void TeamBalancer::RequestSpecificTeam(NetworkID memberId, TeamId desiredTeam)
128 {
129  bool foundMatch=false;
130  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
131  {
132  if (myTeamMembers[i].memberId==memberId)
133  {
134  foundMatch=true;
135  if (myTeamMembers[i].requestedTeam==desiredTeam && myTeamMembers[i].currentTeam==desiredTeam)
136  {
137  return;
138  }
139  else
140  {
141  myTeamMembers[i].requestedTeam=desiredTeam;
142  }
143  }
144  }
145 
146  if (foundMatch==false)
147  {
148  MyTeamMembers mtm;
149  mtm.currentTeam=UNASSIGNED_TEAM_ID;
150  mtm.memberId=memberId;
151  mtm.requestedTeam=desiredTeam;
153  }
154 
155  // Send desiredTeam to the current host.
156  // Also flag that we have requested a team, and record desiredTeam in case the host changes and it needs to be resent.
157  BitStream bsOut;
159  bsOut.Write((MessageID)ID_REQUEST_SPECIFIC_TEAM);
160  bsOut.Write(memberId);
161  bsOut.Write(desiredTeam);
163 }
165 {
166  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
167  {
168  if (myTeamMembers[i].memberId==memberId)
169  {
170  myTeamMembers[i].requestedTeam=UNASSIGNED_TEAM_ID;
171 
172  // Send packet to the host to remove our request flag.
173  BitStream bsOut;
174  bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL);
175  bsOut.Write((MessageID)ID_CANCEL_TEAM_REQUEST);
176  bsOut.Write(memberId);
178 
179  return;
180  }
181  }
182 }
184 {
185  bool foundMatch=false;
186 
187  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
188  {
189  if (myTeamMembers[i].memberId==memberId)
190  {
191  foundMatch=true;
192  if (myTeamMembers[i].currentTeam!=UNASSIGNED_TEAM_ID)
193  return;
194  else
195  myTeamMembers[i].requestedTeam=UNASSIGNED_TEAM_ID;
196  break;
197  }
198  }
199 
200  if (foundMatch==false)
201  {
202  MyTeamMembers mtm;
203  mtm.currentTeam=UNASSIGNED_TEAM_ID;
204  mtm.memberId=memberId;
205  mtm.requestedTeam=UNASSIGNED_TEAM_ID;
207  }
208 
209  // Else send to the current host that we need a team.
210  BitStream bsOut;
211  bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL);
212  bsOut.Write((MessageID)ID_REQUEST_ANY_TEAM);
213  bsOut.Write(memberId);
215 }
217 {
218  // Return team returned by last ID_TEAM_BALANCER_TEAM_ASSIGNED packet
219  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
220  {
221  if (myTeamMembers[i].memberId==memberId)
222  {
223  return myTeamMembers[i].currentTeam;
224  }
225  }
226 
227  return UNASSIGNED_TEAM_ID;
228 }
230 {
231  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
232  {
233  if (myTeamMembers[i].memberId==memberId)
234  {
236  break;
237  }
238  }
239 
240  for (unsigned int i=0; i < teamMembers.Size(); i++)
241  {
242  if (teamMembers[i].memberId==memberId)
243  {
244  RemoveTeamMember(i);
245  break;
246  }
247  }
248 }
250 {
251  switch (packet->data[0])
252  {
253  case ID_FCM2_NEW_HOST:
254  {
255  hostGuid=packet->guid;
256 
257  if (myTeamMembers.Size()>0)
258  {
259  BitStream bsOut;
260  bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL);
261  bsOut.Write((MessageID)ID_STATUS_UPDATE_TO_NEW_HOST);
262 
264  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
265  {
266  bsOut.Write(myTeamMembers[i].memberId);
267  bsOut.Write(myTeamMembers[i].currentTeam);
268  bsOut.Write(myTeamMembers[i].requestedTeam);
269  }
271  }
272  }
273  break;
274  case ID_TEAM_BALANCER_INTERNAL:
275  {
276  if (packet->length>=2)
277  {
278  switch (packet->data[1])
279  {
280  case ID_STATUS_UPDATE_TO_NEW_HOST:
281  OnStatusUpdateToNewHost(packet);
282  break;
283  case ID_CANCEL_TEAM_REQUEST:
284  OnCancelTeamRequest(packet);
285  break;
286  case ID_REQUEST_ANY_TEAM:
287  OnRequestAnyTeam(packet);
288  break;
289  case ID_REQUEST_SPECIFIC_TEAM:
290  OnRequestSpecificTeam(packet);
291  break;
292  }
293  }
294  }
296 
298  {
299  return OnTeamAssigned(packet);
300  }
301 
303  {
304  return OnRequestedTeamChangePending(packet);
305  }
306 
308  {
309  return OnTeamsLocked(packet);
310  }
311  }
312 
313  // Got RequestSpecificTeam
314  // If teams are locked
315  // - If this user already has a team, return ID_TEAM_BALANCER_TEAMS_LOCKED
316  // - This user does not already have a team. Assign a team as if the user called RequestAnyTeam(), with a preference for the requested team. Return ID_TEAM_BALANCER_TEAM_ASSIGNED once the team has been assigned.
317  // If teams are not locked
318  // - If even team balancing is on, only assign this user if this would not cause teams to be unbalanced. If teams WOULD be unbalanced, then flag this user as wanting to join this team. Return ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING
319  // - If the destination team is full, flag this user as wanting to join this team. Return ID_TEAM_BALANCER_REQUESTED_TEAM_CHANGE_PENDING
320  // - Else, join this team. Return ID_TEAM_BALANCER_TEAM_ASSIGNED
321 
322  // Got RequestAnyTeam
323  // Put user on a team following the algorithm. No team is set as preferred.
324 
325  return RR_CONTINUE_PROCESSING;
326 }
327 void TeamBalancer::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
328 {
329  (void) systemAddress;
330  (void) lostConnectionReason;
331 
332  RemoveByGuid(rakNetGUID);
333 }
334 void TeamBalancer::OnAttach(void)
335 {
337 }
338 void TeamBalancer::RemoveByGuid(RakNetGUID rakNetGUID)
339 {
340  // If we are the host, and the closed connection has a team, and teams are not locked:
341  if (WeAreHost())
342  {
343  unsigned int droppedMemberIndex=0;
344  while (droppedMemberIndex < teamMembers.Size())
345  {
346  if (teamMembers[droppedMemberIndex].memberGuid==rakNetGUID)
347  {
348  TeamId droppedTeam = teamMembers[droppedMemberIndex].currentTeam;
349  RemoveTeamMember(droppedMemberIndex);
350  if (lockTeams==false)
351  {
352  if (forceTeamsToBeEven)
353  {
354  // - teams were forced to be even, then run the even team algorithm
355  EvenTeams();
356  }
357  else
358  {
359  // - teams were NOT forced to be even, and the team the dropped player on was full, then move users wanting to join that team (if any)
360  if (teamMemberCounts[ droppedTeam ]==teamLimits[ droppedTeam ]-1)
361  {
362  MoveMemberThatWantsToJoinTeam(droppedTeam);
363  }
364  }
365  }
366  }
367  else
368  {
369  droppedMemberIndex++;
370  }
371  }
372  }
373 }
375 {
376  if (WeAreHost()==false)
377  return;
378 
379  BitStream bsIn(packet->data,packet->length,false);
380  bsIn.IgnoreBytes(2);
381  uint8_t requestedTeamChangeListSize;
382  bsIn.Read(requestedTeamChangeListSize);
383  TeamMember tm;
384  tm.memberGuid=packet->guid;
385  for (uint8_t i=0; i < requestedTeamChangeListSize; i++)
386  {
387  bsIn.Read(tm.memberId);
388  bsIn.Read(tm.currentTeam);
389  bsIn.Read(tm.requestedTeam);
390 
391  if (tm.currentTeam!=UNASSIGNED_TEAM_ID && tm.currentTeam>teamLimits.Size())
392  {
393  RakAssert("Current team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0);
394  return;
395  }
396 
397  if (tm.requestedTeam!=UNASSIGNED_TEAM_ID && tm.requestedTeam>teamLimits.Size())
398  {
399  RakAssert("Requested team out of range in TeamBalancer::OnStatusUpdateToNewHost" && 0);
400  return;
401  }
402 
403  if (tm.currentTeam==UNASSIGNED_TEAM_ID && tm.requestedTeam==UNASSIGNED_TEAM_ID)
404  return;
405 
406  unsigned int memberIndex = GetMemberIndex(tm.memberId, packet->guid);
407  if (memberIndex==(unsigned int) -1)
408  {
409  // Add this system (by GUID) to the list of members if he is not already there
410  // Also update his requested team flag.
411  // Do not process balancing on requested teams, since we don't necessarily have all data from all systems yet and hopefully the state during the host migration was stable.
412  if (tm.currentTeam==UNASSIGNED_TEAM_ID)
413  {
414  // Assign a default team, then add team member
415  if (tm.requestedTeam==UNASSIGNED_TEAM_ID)
416  {
417  // Assign a default team
418  tm.currentTeam=GetNextDefaultTeam();
419  }
420  else
421  {
422  // Assign to requested team if possible. Otherwise, assign to a default team
423  if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false)
424  {
425  tm.currentTeam=tm.requestedTeam;
426  }
427  else
428  {
429  tm.currentTeam=GetNextDefaultTeam();
430  }
431  }
432  }
433 
434  if (tm.currentTeam==UNASSIGNED_TEAM_ID)
435  {
436  RakAssert("Too many members asking for teams!" && 0);
437  return;
438  }
440  }
441  }
442 }
444 {
445  if (WeAreHost()==false)
446  return;
447 
448  BitStream bsIn(packet->data,packet->length,false);
449  bsIn.IgnoreBytes(2);
450  NetworkID memberId;
451  bsIn.Read(memberId);
452 
453  unsigned int memberIndex = GetMemberIndex(memberId, packet->guid);
454  if (memberIndex!=(unsigned int)-1)
455  teamMembers[memberIndex].requestedTeam=UNASSIGNED_TEAM_ID;
456 }
458 {
459  if (WeAreHost()==false)
460  return;
461 
462  BitStream bsIn(packet->data,packet->length,false);
463  bsIn.IgnoreBytes(2);
464  NetworkID memberId;
465  bsIn.Read(memberId);
466 
467  unsigned int memberIndex = GetMemberIndex(memberId, packet->guid);
468  if (memberIndex==(unsigned int)-1)
469  {
470  TeamMember tm;
471  tm.currentTeam=GetNextDefaultTeam();
472  tm.requestedTeam=UNASSIGNED_TEAM_ID;
473  tm.memberGuid=packet->guid;
474  tm.memberId=memberId;
475  if (tm.currentTeam==UNASSIGNED_TEAM_ID)
476  {
477  RakAssert("Too many members asking for teams!" && 0);
478  return;
479  }
481  }
482 }
484 {
485  if (WeAreHost()==false)
486  return;
487 
488  BitStream bsIn(packet->data,packet->length,false);
489  bsIn.IgnoreBytes(2);
490  TeamMember tm;
491  bsIn.Read(tm.memberId);
492  bsIn.Read(tm.requestedTeam);
493 
494  unsigned int memberIndex = GetMemberIndex(tm.memberId, packet->guid);
495  if (tm.requestedTeam==UNASSIGNED_TEAM_ID)
496  {
497  NotifyNoTeam(tm.memberId, packet->guid);
498  if (memberIndex != (unsigned int) -1)
499  RemoveTeamMember(memberIndex);
500  return;
501  }
502 
503  if (tm.requestedTeam>teamLimits.Size())
504  {
505  RakAssert("Requested team out of range in TeamBalancer::OnRequestSpecificTeam" && 0);
506  return;
507  }
508  if (memberIndex==(unsigned int) -1)
509  {
510  tm.memberGuid=packet->guid;
511 
512  // Assign to requested team if possible. Otherwise, assign to a default team
513  if (TeamWouldBeOverpopulatedOnAddition(tm.requestedTeam, teamMembers.Size())==false)
514  {
515  tm.currentTeam=tm.requestedTeam;
516  tm.requestedTeam=UNASSIGNED_TEAM_ID;
517  }
518  else
519  {
520  tm.currentTeam=GetNextDefaultTeam();
521  }
522  if (tm.currentTeam==UNASSIGNED_TEAM_ID)
523  {
524  RakAssert("Too many members asking for teams!" && 0);
525  return;
526  }
528  }
529  else
530  {
531  teamMembers[memberIndex].requestedTeam=tm.requestedTeam;
532  TeamId oldTeamThisUserWasOn = teamMembers[memberIndex].currentTeam;
533 
534  if (lockTeams)
535  {
536  NotifyTeamsLocked(packet->guid, tm.requestedTeam);
537  return;
538  }
539 
540  // Assign to requested team if possible. Otherwise, assign to a default team
541  if (TeamsWouldBeEvenOnSwitch(tm.requestedTeam,oldTeamThisUserWasOn)==true)
542  {
543  SwitchMemberTeam(memberIndex,tm.requestedTeam);
544  NotifyTeamAssigment(memberIndex);
545  }
546  else
547  {
548  // If someone wants to join this user's old team, and we want to join their team, they can swap
549  unsigned int swappableMemberIndex;
550  for (swappableMemberIndex=0; swappableMemberIndex < teamMembers.Size(); swappableMemberIndex++)
551  {
552  if (teamMembers[swappableMemberIndex].currentTeam==tm.requestedTeam && teamMembers[swappableMemberIndex].requestedTeam==oldTeamThisUserWasOn)
553  break;
554  }
555 
556  if (swappableMemberIndex!=teamMembers.Size())
557  {
558  SwapTeamMembersByRequest(memberIndex,swappableMemberIndex);
559  NotifyTeamAssigment(memberIndex);
560  NotifyTeamAssigment(swappableMemberIndex);
561  }
562  else
563  {
564  // Full or would not be even
565  NotifyTeamSwitchPending(packet->guid, tm.requestedTeam, tm.memberId);
566  }
567  }
568  }
569 }
570 unsigned int TeamBalancer::GetMemberIndex(NetworkID memberId, RakNetGUID guid) const
571 {
572  for (unsigned int i=0; i < teamMembers.Size(); i++)
573  {
574  if (teamMembers[i].memberGuid==guid && teamMembers[i].memberId==memberId)
575  return i;
576  }
577  return (unsigned int) -1;
578 }
579 unsigned int TeamBalancer::AddTeamMember(const TeamMember &tm)
580 {
581  if (tm.currentTeam>teamLimits.Size())
582  {
583  RakAssert("TeamBalancer::AddTeamMember team index out of bounds" && 0);
584  return (unsigned int) -1;
585  }
586 
587  RakAssert(tm.currentTeam!=UNASSIGNED_TEAM_ID);
588 
590  if (teamMemberCounts.Size()<tm.currentTeam)
591  teamMemberCounts.Replace(1,0,tm.currentTeam,_FILE_AND_LINE_);
592  else
593  teamMemberCounts[tm.currentTeam]=teamMemberCounts[tm.currentTeam]+1;
594  return teamMembers.Size()-1;
595 }
596 void TeamBalancer::RemoveTeamMember(unsigned int index)
597 {
598  RakAssert( teamMemberCounts[ teamMembers[index].currentTeam ] != 0);
599  teamMemberCounts[ teamMembers[index].currentTeam ]=teamMemberCounts[ teamMembers[index].currentTeam ]-1;
600  teamMembers.RemoveAtIndexFast(index);
601 }
602 void TeamBalancer::GetMinMaxTeamMembers(int &minMembersOnASingleTeam, int &maxMembersOnASingleTeam)
603 {
604  minMembersOnASingleTeam = teamMembers.Size()/teamLimits.Size();
605  if ((teamMembers.Size() % teamLimits.Size()) == 0)
606  maxMembersOnASingleTeam = minMembersOnASingleTeam;
607  else
608  maxMembersOnASingleTeam = minMembersOnASingleTeam+1;
609 }
610 void TeamBalancer::EvenTeams(void)
611 {
612  // Ensure all teams are even. If not, pick players at random from overpopulated teams, and move to underpopulated teams.
613  int minMembersOnASingleTeam;
614  int maxMembersOnASingleTeam;
615  GetMinMaxTeamMembers(minMembersOnASingleTeam,maxMembersOnASingleTeam);
616 
617  // First select among players that have requested to switch teams, if any, before choosing players that did not want to switch teams.
618  // Players that are moved should be notified of ID_TEAM_BALANCER_TEAM_ASSIGNED
619  DataStructures::List<TeamId> overpopulatedTeams;
620  TeamId teamMemberCountsIndex;
621  unsigned int memberIndexToSwitch;
622  for (teamMemberCountsIndex=0; teamMemberCountsIndex<teamMemberCounts.Size(); teamMemberCountsIndex++)
623  {
624  while (teamMemberCounts[teamMemberCountsIndex]<minMembersOnASingleTeam && teamMemberCounts[teamMemberCountsIndex]<teamLimits[teamMemberCountsIndex])
625  {
626  GetOverpopulatedTeams(overpopulatedTeams,maxMembersOnASingleTeam);
627  RakAssert(overpopulatedTeams.Size()>0);
628  memberIndexToSwitch=GetMemberIndexToSwitchTeams(overpopulatedTeams,teamMemberCountsIndex);
629  RakAssert(memberIndexToSwitch!=(unsigned int)-1);
630  SwitchMemberTeam(memberIndexToSwitch,teamMemberCountsIndex);
631  // Tell this member he switched teams
632  NotifyTeamAssigment(memberIndexToSwitch);
633  }
634  }
635 }
636 unsigned int TeamBalancer::GetMemberIndexToSwitchTeams(const DataStructures::List<TeamId> &sourceTeamNumbers, TeamId targetTeamNumber)
637 {
638  DataStructures::List<unsigned int> preferredSwapIndices;
639  DataStructures::List<unsigned int> potentialSwapIndices;
640  unsigned int i,j;
641  for (j=0; j < sourceTeamNumbers.Size(); j++)
642  {
643  RakAssert(sourceTeamNumbers[j]!=targetTeamNumber);
644  for (i=0; i < teamMembers.Size(); i++)
645  {
646  if (teamMembers[i].currentTeam==sourceTeamNumbers[j])
647  {
648  if (teamMembers[i].requestedTeam==targetTeamNumber)
649  preferredSwapIndices.Push(i,_FILE_AND_LINE_);
650  else
651  potentialSwapIndices.Push(i,_FILE_AND_LINE_);
652  }
653  }
654  }
655 
656  if (preferredSwapIndices.Size()>0)
657  {
658  return preferredSwapIndices[ randomMT() % preferredSwapIndices.Size() ];
659  }
660  else if (potentialSwapIndices.Size()>0)
661  {
662  return potentialSwapIndices[ randomMT() % potentialSwapIndices.Size() ];
663  }
664  else
665  {
666  return (unsigned int) -1;
667  }
668 }
669 void TeamBalancer::SwitchMemberTeam(unsigned int teamMemberIndex, TeamId destinationTeam)
670 {
671  teamMemberCounts[ teamMembers[teamMemberIndex].currentTeam ]=teamMemberCounts[ teamMembers[teamMemberIndex].currentTeam ]-1;
672  teamMemberCounts[ destinationTeam ]=teamMemberCounts[ destinationTeam ]+1;
673  teamMembers[teamMemberIndex].currentTeam=destinationTeam;
674  if (teamMembers[teamMemberIndex].requestedTeam==destinationTeam)
675  teamMembers[teamMemberIndex].requestedTeam=UNASSIGNED_TEAM_ID;
676 }
677 void TeamBalancer::GetOverpopulatedTeams(DataStructures::List<TeamId> &overpopulatedTeams, int maxTeamSize)
678 {
679  overpopulatedTeams.Clear(true,_FILE_AND_LINE_);
680  for (TeamId i=0; i < teamMemberCounts.Size(); i++)
681  {
682  if (teamMemberCounts[i]>=maxTeamSize)
683  overpopulatedTeams.Push(i,_FILE_AND_LINE_);
684  }
685 }
686 void TeamBalancer::NotifyTeamAssigment(unsigned int teamMemberIndex)
687 {
688  RakAssert(teamMemberIndex < teamMembers.Size());
689  if (teamMemberIndex>=teamMembers.Size())
690  return;
691 
692  BitStream bsOut;
694  bsOut.Write(teamMembers[teamMemberIndex].currentTeam);
695  bsOut.Write(teamMembers[teamMemberIndex].memberId);
696  rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,teamMembers[teamMemberIndex].memberGuid,false);
697 }
698 bool TeamBalancer::WeAreHost(void) const
699 {
701 }
703 {
704  if (packet->guid!=hostGuid)
706 
707  BitStream bsIn(packet->data,packet->length,false);
708  bsIn.IgnoreBytes(1);
709 
710  MyTeamMembers mtm;
711  bsIn.Read(mtm.currentTeam);
712  bsIn.Read(mtm.memberId);
713  mtm.requestedTeam=UNASSIGNED_TEAM_ID;
714 
715  bool foundMatch=false;
716  for (unsigned int i=0; i < myTeamMembers.Size(); i++)
717  {
718  if (myTeamMembers[i].memberId==mtm.memberId)
719  {
720  foundMatch=true;
721  if (myTeamMembers[i].requestedTeam==mtm.currentTeam)
722  myTeamMembers[i].requestedTeam=UNASSIGNED_TEAM_ID;
723  myTeamMembers[i].currentTeam=mtm.currentTeam;
724  break;
725  }
726  }
727 
728  if (foundMatch==false)
729  {
731  }
732 
733  return RR_CONTINUE_PROCESSING;
734 }
736 {
737  if (packet->guid!=hostGuid)
739 
740  return RR_CONTINUE_PROCESSING;
741 }
743 {
744  if (packet->guid!=hostGuid)
746 
747  return RR_CONTINUE_PROCESSING;
748 }
750 {
751  // Accounting for team balancing and team limits, get the team a player should be placed on
753  {
754  case SMALLEST_TEAM:
755  {
756  return GetSmallestNonFullTeam();
757  }
758 
759  case FILL_IN_ORDER:
760  {
761  return GetFirstNonFullTeam();
762  }
763 
764  default:
765  {
766  RakAssert("TeamBalancer::GetNextDefaultTeam unknown algorithm enumeration" && 0);
767  return UNASSIGNED_TEAM_ID;
768  }
769  }
770 }
771 bool TeamBalancer::TeamWouldBeOverpopulatedOnAddition(TeamId teamId, unsigned int teamMemberSize)
772 {
773  // Accounting for team balancing and team limits, would this team be overpopulated if a member was added to it?
774  if (teamMemberCounts[teamId]>=teamLimits[teamId])
775  {
776  return true;
777  }
778 
779  if (forceTeamsToBeEven)
780  {
781  int allowedLimit = teamMemberSize/teamLimits.Size() + 1;
782  return teamMemberCounts[teamId]>=allowedLimit;
783  }
784 
785  return false;
786 }
787 bool TeamBalancer::TeamWouldBeUnderpopulatedOnLeave(TeamId teamId, unsigned int teamMemberSize)
788 {
789  if (forceTeamsToBeEven)
790  {
791  unsigned int minMembersOnASingleTeam = (teamMemberSize-1)/teamLimits.Size();
792  return teamMemberCounts[teamId]<=minMembersOnASingleTeam;
793  }
794  return false;
795 }
797 {
798  TeamId idx;
799  unsigned long smallestTeamCount=MAX_UNSIGNED_LONG;
800  TeamId smallestTeamIndex = UNASSIGNED_TEAM_ID;
801  for (idx=0; idx < teamMemberCounts.Size(); idx++)
802  {
803  if (teamMemberCounts[idx]<smallestTeamCount && teamMemberCounts[idx]<teamLimits[idx])
804  {
805  smallestTeamCount=teamMemberCounts[idx];
806  smallestTeamIndex=idx;
807  }
808  }
809  return smallestTeamIndex;
810 }
812 {
813  TeamId idx;
814  for (idx=0; idx < teamMemberCounts.Size(); idx++)
815  {
816  if (teamMemberCounts[idx]<teamLimits[idx])
817  {
818  return idx;
819  }
820  }
821  return UNASSIGNED_TEAM_ID;
822 }
824 {
825  RakAssert(forceTeamsToBeEven==false && lockTeams==false);
826 
827  do
828  {
829  teamId = MoveMemberThatWantsToJoinTeamInternal(teamId);
830  } while (teamId!=UNASSIGNED_TEAM_ID);
831 }
833 {
834  DataStructures::List<TeamId> membersThatWantToJoinTheTeam;
835  for (TeamId i=0; i < teamMembers.Size(); i++)
836  {
837  if (teamMembers[i].requestedTeam==teamId)
838  membersThatWantToJoinTheTeam.Push(i,_FILE_AND_LINE_);
839  }
840 
841  if (membersThatWantToJoinTheTeam.Size()>0)
842  {
843  TeamId oldTeam;
844  unsigned int swappedMemberIndex = membersThatWantToJoinTheTeam[ randomMT() % membersThatWantToJoinTheTeam.Size() ];
845  oldTeam=teamMembers[swappedMemberIndex].currentTeam;
846  SwitchMemberTeam(swappedMemberIndex,teamId);
847  NotifyTeamAssigment(swappedMemberIndex);
848  return oldTeam;
849  }
850  return UNASSIGNED_TEAM_ID;
851 }
852 void TeamBalancer::NotifyTeamsLocked(RakNetGUID target, TeamId requestedTeam)
853 {
854  BitStream bsOut;
856  bsOut.Write(requestedTeam);
857  rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false);
858 }
859 void TeamBalancer::NotifyTeamSwitchPending(RakNetGUID target, TeamId requestedTeam, NetworkID memberId)
860 {
861  BitStream bsOut;
863  bsOut.Write(requestedTeam);
864  bsOut.Write(memberId);
865  rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false);
866 }
867 void TeamBalancer::SwapTeamMembersByRequest(unsigned int memberIndex1, unsigned int memberIndex2)
868 {
869  TeamId index1Team = teamMembers[memberIndex1].currentTeam;
870  teamMembers[memberIndex1].currentTeam=teamMembers[memberIndex2].currentTeam;
871  teamMembers[memberIndex2].currentTeam=index1Team;
872  teamMembers[memberIndex1].requestedTeam=UNASSIGNED_TEAM_ID;
873  teamMembers[memberIndex2].requestedTeam=UNASSIGNED_TEAM_ID;
874 }
875 void TeamBalancer::NotifyNoTeam(NetworkID memberId, RakNetGUID target)
876 {
877  BitStream bsOut;
878  bsOut.Write((MessageID)ID_TEAM_BALANCER_TEAM_ASSIGNED);
879  bsOut.Write((unsigned char)UNASSIGNED_TEAM_ID);
880  bsOut.Write(memberId);
881  rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,target,false);
882 }
884 {
885  RakAssert(teamMembers.Size()!=0);
886  return TeamWouldBeOverpopulatedOnAddition(t1, teamMembers.Size()-1)==false &&
887  TeamWouldBeUnderpopulatedOnLeave(t2, teamMembers.Size()-1)==false;
888 }
889 
890 #endif // _RAKNET_SUPPORT_*