SLikeNet 0.1.3

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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
16 #include "slikenet/NativeFeatureIncludes.h"
17 #if _RAKNET_SUPPORT_TeamBalancer==1
18
19 #include "slikenet/TeamBalancer.h"
20 #include "slikenet/BitStream.h"
21 #include "slikenet/MessageIdentifiers.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
35 STATIC_FACTORY_DEFINITIONS(TeamBalancer,TeamBalancer);
36
37 TeamBalancer::TeamBalancer()
38 {
39 defaultAssigmentAlgorithm=SMALLEST_TEAM;
40 forceTeamsToBeEven=false;
41 lockTeams=false;
42 hostGuid=UNASSIGNED_RAKNET_GUID;
43 }
44 TeamBalancer::~TeamBalancer()
45 {
46
47 }
48 void TeamBalancer::SetTeamSizeLimit(TeamId team, unsigned short limit)
49 {
50 teamLimits.Replace(limit,0,team,_FILE_AND_LINE_);
51 if (teamLimits.Size() > teamMemberCounts.Size())
52 teamMemberCounts.Replace(0,0,teamLimits.Size()-1,_FILE_AND_LINE_);
53 }
54 void TeamBalancer::SetDefaultAssignmentAlgorithm(DefaultAssigmentAlgorithm daa)
55 {
56 // Just update the default. Currently active teams are not affected.
57 defaultAssigmentAlgorithm=daa;
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 {
96 SwapTeamMembersByRequest(i,j);
97 NotifyTeamAssigment(i);
98 NotifyTeamAssigment(j);
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);
120 NotifyTeamAssigment(i);
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;
152 myTeamMembers.Push(mtm, _FILE_AND_LINE_);
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;
158 bsOut.Write((MessageID)ID_TEAM_BALANCER_INTERNAL);
159 bsOut.Write((MessageID)ID_REQUEST_SPECIFIC_TEAM);
160 bsOut.Write(memberId);
161 bsOut.Write(desiredTeam);
162 rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false);
163 }
164 void TeamBalancer::CancelRequestSpecificTeam(NetworkID memberId)
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);
177 rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false);
178
179 return;
180 }
181 }
182 }
183 void TeamBalancer::RequestAnyTeam(NetworkID memberId)
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;
206 myTeamMembers.Push(mtm, _FILE_AND_LINE_);
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);
214 rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false);
215 }
216 TeamId TeamBalancer::GetMyTeam(NetworkID memberId) const
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 }
229 void TeamBalancer::DeleteMember(NetworkID memberId)
230 {
231 for (unsigned int i=0; i < myTeamMembers.Size(); i++)
232 {
233 if (myTeamMembers[i].memberId==memberId)
234 {
235 myTeamMembers.RemoveAtIndexFast(i);
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 }
249 PluginReceiveResult TeamBalancer::OnReceive(Packet *packet)
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
263 bsOut.WriteCasted<uint8_t>(myTeamMembers.Size());
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 }
270 rakPeerInterface->Send(&bsOut,HIGH_PRIORITY,RELIABLE_ORDERED,0,hostGuid,false);
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 }
295 return RR_STOP_PROCESSING_AND_DEALLOCATE;
296
297 case ID_TEAM_BALANCER_TEAM_ASSIGNED:
298 {
299 return OnTeamAssigned(packet);
300 }
301
302 case ID_TEAM_BALANCER_REQUESTED_TEAM_FULL:
303 {
304 return OnRequestedTeamChangePending(packet);
305 }
306
307 case ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED:
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 {
336 hostGuid = rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
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 }
374 void TeamBalancer::OnStatusUpdateToNewHost(Packet *packet)
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 }
439 NotifyTeamAssigment(AddTeamMember(tm));
440 }
441 }
442 }
443 void TeamBalancer::OnCancelTeamRequest(Packet *packet)
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 }
457 void TeamBalancer::OnRequestAnyTeam(Packet *packet)
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 }
480 NotifyTeamAssigment(AddTeamMember(tm));
481 }
482 }
483 void TeamBalancer::OnRequestSpecificTeam(Packet *packet)
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 }
527 NotifyTeamAssigment(AddTeamMember(tm));
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
589 teamMembers.Push(tm,_FILE_AND_LINE_);
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;
693 bsOut.Write((MessageID)ID_TEAM_BALANCER_TEAM_ASSIGNED);
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 {
700 return hostGuid==rakPeerInterface->GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS);
701 }
702 PluginReceiveResult TeamBalancer::OnTeamAssigned(Packet *packet)
703 {
704 if (packet->guid!=hostGuid)
705 return RR_STOP_PROCESSING_AND_DEALLOCATE;
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 {
730 return RR_STOP_PROCESSING_AND_DEALLOCATE;
731 }
732
733 return RR_CONTINUE_PROCESSING;
734 }
735 PluginReceiveResult TeamBalancer::OnRequestedTeamChangePending(Packet *packet)
736 {
737 if (packet->guid!=hostGuid)
738 return RR_STOP_PROCESSING_AND_DEALLOCATE;
739
740 return RR_CONTINUE_PROCESSING;
741 }
742 PluginReceiveResult TeamBalancer::OnTeamsLocked(Packet *packet)
743 {
744 if (packet->guid!=hostGuid)
745 return RR_STOP_PROCESSING_AND_DEALLOCATE;
746
747 return RR_CONTINUE_PROCESSING;
748 }
749 TeamId TeamBalancer::GetNextDefaultTeam(void)
750 {
751 // Accounting for team balancing and team limits, get the team a player should be placed on
752 switch (defaultAssigmentAlgorithm)
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 }
796 TeamId TeamBalancer::GetSmallestNonFullTeam(void) const
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 }
811 TeamId TeamBalancer::GetFirstNonFullTeam(void) const
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 }
823 void TeamBalancer::MoveMemberThatWantsToJoinTeam(TeamId teamId)
824 {
825 RakAssert(forceTeamsToBeEven==false && lockTeams==false);
826
827 do
828 {
829 teamId = MoveMemberThatWantsToJoinTeamInternal(teamId);
830 } while (teamId!=UNASSIGNED_TEAM_ID);
831 }
832 TeamId TeamBalancer::MoveMemberThatWantsToJoinTeamInternal(TeamId teamId)
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;
855 bsOut.Write((MessageID)ID_TEAM_BALANCER_REQUESTED_TEAM_LOCKED);
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;
862 bsOut.Write((MessageID)ID_TEAM_BALANCER_REQUESTED_TEAM_FULL);
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 }
883 bool TeamBalancer::TeamsWouldBeEvenOnSwitch(TeamId t1, TeamId t2)
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_*