Richard Boegli's CnC_Generals_Zero_Hour Fork WIP
This is documentation of Richard Boegil's Zero Hour Fork
 
Loading...
Searching...
No Matches
generals.cpp
Go to the documentation of this file.
1/*
2** Command & Conquer Generals Zero Hour(tm)
3** Copyright 2025 Electronic Arts Inc.
4**
5** This program is free software: you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation, either version 3 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef _WIN32
20#include <process.h>
21#endif
22
23#include <wstring.h>
24#include <tcp.h>
25#include <wdebug.h>
26#include "mydebug.h"
27
28#include <ghttp/ghttp.h>
29
30#include <cmath>
31#include <cstdlib>
32#include <algorithm>
33#include <strstream>
34
35#ifdef _UNIX
36using namespace std;
37#endif
38
39/*
40#ifdef IN
41#undef IN
42#endif
43#define IN const
44*/
45#include "generals.h"
46/*
47#ifdef IN
48#undef IN
49#endif
50#define IN const
51*/
52#include "global.h"
53/*
54#ifdef IN
55#undef IN
56#endif
57#define IN const
58*/
59
60std::string intToString(int val)
61{
62 std::string s = "";
63 bool neg = (val < 0);
64 if (val < 0)
65 {
66 val = -val;
67 }
68 if (val == 0)
69 return "0";
70
71 char buf[2];
72 buf[1] = 0;
73 while (val)
74 {
75 buf[0] = '0' + val%10;
76 val /= 10;
77 s.insert(0, buf);
78 }
79 if (neg)
80 s.insert(0, "-");
81 return s;
82}
83
84std::string uintToString(unsigned int val)
85{
86 std::string s = "";
87 if (val == 0)
88 return "0";
89
90 char buf[2];
91 buf[1] = 0;
92 while (val)
93 {
94 buf[0] = '0' + val%10;
95 val /= 10;
96 s.insert(0, buf);
97 }
98 return s;
99}
100
102{
103 MapBitSet c;
104 if (a.size() != b.size())
105 return c;
106
107 for (int i=0; i<(int)a.size(); ++i)
108 {
109 c.push_back(a[i] && b[i]);
110 }
111
112 return c;
113}
114
116{
117 int count=0;
118 for (int i=0; i<(int)a.size(); ++i)
119 {
120 //DBGMSG(a[i]);
121 if (a[i])
122 ++count;
123 }
124 return count;
125}
126
127// =====================================================================
128// Users
129// =====================================================================
130
132{
134 points = 1;
135 minPoints = maxPoints = 100;
136 country = color = -1;
137 pseudoPing.clear();
138 matchStart = time(NULL);
139 timeToWiden = 0;
140 widened = false;
141 numPlayers = 2;
142 discons = maxDiscons = 2;
143 maps.clear();
144 maxPing = 1000;
145}
146
147static const int MaxPingValue = 255*255*2;
148
150{
151 if (!a || !b || a->pseudoPing.size() != b->pseudoPing.size())
152 return MaxPingValue; // Max ping
153
154 int bestPing = MaxPingValue;
155 for (int i=0; i<(int)a->pseudoPing.size(); ++i)
156 {
157 int p1, p2;
158 p1 = a->pseudoPing[i];
159 p2 = b->pseudoPing[i];
160
161 if (p1 * p1 + p2 * p2 < bestPing)
162 bestPing = p1 * p1 + p2 * p2;
163 }
164
165 return (int)sqrt(bestPing);
166}
167
168
169// =====================================================================
170// Matcher thread
171// =====================================================================
172
174{
175 // Read some values from the config file
176 int quietTMP = 0;
177 Global.config.getInt("NOECHO", quietTMP);
178 if (quietTMP)
179 quiet = true;
180 else
181 quiet = false;
182
183 // Grab the weights for different parameters
184 Global.config.getInt("MATCH_WEIGHT_LOWPING", weightLowPing, "GENERALS");
185 Global.config.getInt("MATCH_WEIGHT_AVGPOINTS", weightAvgPoints, "GENERALS");
186 totalWeight = weightLowPing + weightAvgPoints;
187
188 INFMSG("weightLowPing = " << weightLowPing);
189 INFMSG("weightAvgPoints = " << weightAvgPoints);
190 INFMSG("totalWeight = " << totalWeight);
191
192 Global.config.getInt("SecondsBetweenPoolSizeAnnouncements", m_secondsBetweenPoolSizeAnnouncements, NULL);
193 if (m_secondsBetweenPoolSizeAnnouncements < 10)
194 {
195 m_secondsBetweenPoolSizeAnnouncements = 10;
196 }
197 m_nextPoolSizeAnnouncement = time(NULL);
198}
199
201{}
202
203
204#define W(x) setw(x) <<
205void GeneralsMatcher::dumpUsers(void)
206{}
207
208
209
210void GeneralsMatcher::sendMatchInfo(std::string name1, std::string name2, std::string name3, std::string name4,
211 std::string name5, std::string name6, std::string name7, std::string name8,
212 GeneralsUser *user1, GeneralsUser *user2, GeneralsUser *user3, GeneralsUser *user4,
213 GeneralsUser *user5, GeneralsUser *user6, GeneralsUser *user7, GeneralsUser *user8,
214 int numPlayers, int ladderID)
215{
216 MapBitSet tmp = MapSetUnion(user1->maps, user2->maps);
217 if (numPlayers > 2)
218 {
219 tmp = MapSetUnion(tmp, user3->maps);
220 tmp = MapSetUnion(tmp, user4->maps);
221 }
222 if (numPlayers > 4)
223 {
224 tmp = MapSetUnion(tmp, user5->maps);
225 tmp = MapSetUnion(tmp, user6->maps);
226 }
227 if (numPlayers > 6)
228 {
229 tmp = MapSetUnion(tmp, user7->maps);
230 tmp = MapSetUnion(tmp, user8->maps);
231 }
232
233 int numMaps = MapSetCount(tmp);
234
235 if (!numMaps)
236 {
237 DBGMSG("No maps!");
238 user1->status = STATUS_WORKING;
239 user2->status = STATUS_WORKING;
240 if (numPlayers > 2)
241 {
242 user3->status = STATUS_WORKING;
243 user4->status = STATUS_WORKING;
244 }
245 if (numPlayers > 4)
246 {
247 user5->status = STATUS_WORKING;
248 user6->status = STATUS_WORKING;
249 }
250 if (numPlayers > 6)
251 {
252 user7->status = STATUS_WORKING;
253 user8->status = STATUS_WORKING;
254 }
255 return;
256 }
257 user1->status = STATUS_MATCHED;
258 user2->status = STATUS_MATCHED;
259 if (numPlayers > 2)
260 {
261 user3->status = STATUS_MATCHED;
262 user4->status = STATUS_MATCHED;
263 }
264 if (numPlayers > 4)
265 {
266 user5->status = STATUS_MATCHED;
267 user6->status = STATUS_MATCHED;
268 }
269 if (numPlayers > 6)
270 {
271 user7->status = STATUS_MATCHED;
272 user8->status = STATUS_MATCHED;
273 }
274
275 int whichMap = Global.rnd.Int(0, RAND_MAX-1);
276 DBGMSG(whichMap);
277 whichMap = whichMap%numMaps;
278 DBGMSG(whichMap);
279 ++whichMap;
280 DBGMSG(whichMap);
281 DBGMSG("Random map #" << whichMap << "/" << numMaps);
282
283 int i;
284 for (i=0; i<(int)user1->maps.size(); ++i)
285 {
286 if (tmp[i])
287 --whichMap;
288 if (whichMap == 0)
289 break;
290 }
291 DBGMSG("Playing on map in pos " << i);
292
293 std::string s;
294 s = "MBOT:MATCHED ";
295 s.append(intToString(i));
296 s.append(" ");
297 s.append(intToString( Global.rnd.Int(0, RAND_MAX-1) ));
298 s.append(" ");
299 s.append(name1);
300 s.append(" ");
301 s.append(uintToString(user1->IP));
302 s.append(" ");
303 s.append(intToString(user1->country));
304 s.append(" ");
305 s.append(intToString(user1->color));
306 s.append(" ");
307 s.append(intToString(user1->NAT));
308 s.append(" ");
309 s.append(name2);
310 s.append(" ");
311 s.append(uintToString(user2->IP));
312 s.append(" ");
313 s.append(intToString(user2->country));
314 s.append(" ");
315 s.append(intToString(user2->color));
316 s.append(" ");
317 s.append(intToString(user2->NAT));
318 if (user3)
319 {
320 s.append(" ");
321 s.append(name3);
322 s.append(" ");
323 s.append(uintToString(user3->IP));
324 s.append(" ");
325 s.append(intToString(user3->country));
326 s.append(" ");
327 s.append(intToString(user3->color));
328 s.append(" ");
329 s.append(intToString(user3->NAT));
330 }
331 if (user4)
332 {
333 s.append(" ");
334 s.append(name4);
335 s.append(" ");
336 s.append(uintToString(user4->IP));
337 s.append(" ");
338 s.append(intToString(user4->country));
339 s.append(" ");
340 s.append(intToString(user4->color));
341 s.append(" ");
342 s.append(intToString(user4->NAT));
343 }
344 if (user5)
345 {
346 s.append(" ");
347 s.append(name5);
348 s.append(" ");
349 s.append(uintToString(user5->IP));
350 s.append(" ");
351 s.append(intToString(user5->country));
352 s.append(" ");
353 s.append(intToString(user5->color));
354 s.append(" ");
355 s.append(intToString(user5->NAT));
356 }
357 if (user6)
358 {
359 s.append(" ");
360 s.append(name6);
361 s.append(" ");
362 s.append(uintToString(user6->IP));
363 s.append(" ");
364 s.append(intToString(user6->country));
365 s.append(" ");
366 s.append(intToString(user6->color));
367 s.append(" ");
368 s.append(intToString(user6->NAT));
369 }
370 if (user7)
371 {
372 s.append(" ");
373 s.append(name7);
374 s.append(" ");
375 s.append(uintToString(user7->IP));
376 s.append(" ");
377 s.append(intToString(user7->country));
378 s.append(" ");
379 s.append(intToString(user7->color));
380 s.append(" ");
381 s.append(intToString(user7->NAT));
382 }
383 if (user8)
384 {
385 s.append(" ");
386 s.append(name8);
387 s.append(" ");
388 s.append(uintToString(user8->IP));
389 s.append(" ");
390 s.append(intToString(user8->country));
391 s.append(" ");
392 s.append(intToString(user8->color));
393 s.append(" ");
394 s.append(intToString(user8->NAT));
395 }
396
397 std::string n;
398 n = name1;
399 n.append(",");
400 n.append(name2);
401 if (user3)
402 {
403 n.append(",");
404 n.append(name3);
405 }
406 if (user4)
407 {
408 n.append(",");
409 n.append(name4);
410 }
411 if (user5)
412 {
413 n.append(",");
414 n.append(name5);
415 }
416 if (user6)
417 {
418 n.append(",");
419 n.append(name6);
420 }
421 if (user7)
422 {
423 n.append(",");
424 n.append(name7);
425 }
426 if (user8)
427 {
428 n.append(",");
429 n.append(name8);
430 }
431 peerMessagePlayer(m_peer, n.c_str(), s.c_str(), NormalMessage);
432}
433
435{
436 bool showPoolSize = false;
437 time_t now = time(NULL);
438 if (now > m_nextPoolSizeAnnouncement)
439 {
440 m_nextPoolSizeAnnouncement = now + m_secondsBetweenPoolSizeAnnouncements;
441 showPoolSize = true;
442 }
443 checkMatchesInUserMap(m_nonLadderUsers1v1, 0, 2, showPoolSize);
444 checkMatchesInUserMap(m_nonLadderUsers2v2, 0, 4, showPoolSize);
445 checkMatchesInUserMap(m_nonLadderUsers3v3, 0, 6, showPoolSize);
446 checkMatchesInUserMap(m_nonLadderUsers4v4, 0, 8, showPoolSize);
447
448 for (LadderMap::iterator it = m_ladders.begin(); it != m_ladders.end(); ++it)
449 {
450 checkMatchesInUserMap(it->second, it->first, 2, showPoolSize);
451 }
452}
453
454double GeneralsMatcher::computeMatchFitness(const std::string& i1, const GeneralsUser *u1, const std::string& i2, const GeneralsUser *u2)
455{
456 //DBGMSG("matching "<<i1<< " vs "<<i2);
457 if (u1->status != STATUS_WORKING || u2->status != STATUS_WORKING)
458 return 0.0;
459
460 // see if they pinged the same # of servers (sanity).
461 if (u1->pseudoPing.size() != u2->pseudoPing.size())
462 return 0.0;
463
464 // check point percentage ranges
465 int p1 = max(1,u1->points), p2 = max(1,u2->points);
466 double p1percent = (double)p2/(double)p1;
467 double p2percent = (double)p1/(double)p2;
468 //DBGMSG("points: " << p1 << "," << p2 << " - " << p1percent << "," << p2percent);
469 if (!u1->widened && ( p1percent < u1->minPoints || p1percent > u1->maxPoints ))
470 return 0.0;
471
472 if (!u2->widened && ( p2percent < u2->minPoints || p2percent > u2->maxPoints ))
473 return 0.0;
474
475
476 int minP = min(p1, p2);
477 int maxP = max(p1, p2);
478 double pointPercent = (double)minP/(double)maxP;
479 //DBGMSG("\tpointPercent = "<<pointPercent);
480
481 // check pings
482 int pingDelta = calcPingDelta(u1, u2);
483 if (!u1->widened && pingDelta > u1->maxPing)
484 return 0.0;
485 if (!u2->widened && pingDelta > u2->maxPing)
486 return 0.0;
487 //DBGMSG("pingDelta="<<pingDelta);
488
489 //DBGMSG(u1->discons << "," << u1->maxDiscons << " " << u2->discons << "," << u2->maxDiscons);
490 // check discons
491 if (u1->maxDiscons && (!u1->widened && u2->discons > u1->maxDiscons))
492 return 0.0;
493 if (u2->maxDiscons && (!u2->widened && u1->discons > u2->maxDiscons))
494 return 0.0;
495 //DBGMSG("Made it through discons");
496
497 {
498 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
499 if (!MapSetCount(tmp))
500 return 0.0;
501 }
502
503 // they have something in common. calculate match fitness.
504 double matchFitness = ( weightAvgPoints * (1-pointPercent) +
505 weightLowPing * (MaxPingValue - pingDelta)/MaxPingValue ) / (double)totalWeight;
506 //DBGMSG("Match fitness: "<<matchFitness);
507
508 /*
509 DBGMSG(i1->first << " vs " << i2->first << " has fitness " << matchFitness << "\n"
510 "\tpointPercent: " << pointPercent << "\n"
511 "\tpingDelta: " << pingDelta << "\n"
512 "\twidened: " << u1->widened << u2->widened << "\n"
513 "\tweightAvgPoints: " << weightAvgPoints << "\n"
514 "\tweightLowPing: " << weightLowPing << "\n"
515 "\ttotalWeight: " << totalWeight
516 );
517 */
518
519 return matchFitness;
520}
521
522void GeneralsMatcher::checkMatchesInUserMap(UserMap& userMap, int ladderID, int numPlayers, bool showPoolSize)
523{
524 UserMap::iterator i1, i2, i3, i4, i5, i6, i7, i8;
525 GeneralsUser *u1, *u2, *u3, *u4, *u5, *u6, *u7, *u8;
526 static const double fitnessThreshold = 0.3;
527 time_t now = time(NULL);
528
529 std::string s;
530 if (showPoolSize)
531 {
532 s = "MBOT:POOLSIZE ";
533 s.append(intToString(userMap.size()));
534 }
535
536 // iterate through users, timing them out as neccessary
537 for (i1 = userMap.begin(); i1 != userMap.end(); ++i1)
538 {
539 if (showPoolSize)
540 {
541 peerMessagePlayer(m_peer, i1->first.c_str(), s.c_str(), NormalMessage);
542 }
543
544 u1 = i1->second;
545 if (u1->timeToWiden && u1->timeToWiden < now)
546 {
547 u1->timeToWiden = 0;
548 u1->widened = true;
549 for (int m=0; m<(int)u1->maps.size(); ++m)
550 u1->maps[m] = 1;
551 DBGMSG("Widening search for " << i1->first);
552 peerMessagePlayer(m_peer, i1->first.c_str(), "MBOT:WIDENINGSEARCH", NormalMessage);
553 }
554 }
555
556 // iterate through all users, looking for a match
557 for (i1 = userMap.begin(); i1 != userMap.end(); ++i1)
558 {
559 u1 = i1->second;
560 if (u1->status != STATUS_WORKING)
561 continue;
562
563 GeneralsUser *bestUser = NULL;
564 double bestMatchFitness = 0.0;
565 std::string bestName = "";
566
567 // look at everybody left
568 i2 = i1;
569 for (++i2; i2 != userMap.end(); ++i2)
570 {
571 u2 = i2->second;
572 if (u2->status != STATUS_WORKING)
573 continue;
574
575 double matchFitness = computeMatchFitness(i1->first, u1, i2->first, u2);
576 if (matchFitness > fitnessThreshold)
577 {
578 if (numPlayers == 2)
579 {
580 if (matchFitness > bestMatchFitness)
581 {
582 // possibly match 2 players
583 bestMatchFitness = matchFitness;
584 bestUser = u2;
585 bestName = i2->first;
586 }
587 }
588 else
589 {
590 i3 = i2;
591 for (++i3; i3 != userMap.end(); ++i3)
592 {
593 u3 = i3->second;
594 if (u3->status != STATUS_WORKING)
595 continue;
596
597 double matchFitness1 = computeMatchFitness(i1->first, u1, i3->first, u3);
598 double matchFitness2 = computeMatchFitness(i2->first, u2, i3->first, u3);
599 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
600 tmp = MapSetUnion(tmp, u3->maps);
601 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold)
602 {
603 i4 = i3;
604 for (++i4; i4 != userMap.end(); ++i4)
605 {
606 u4 = i4->second;
607 if (u4->status != STATUS_WORKING)
608 continue;
609
610 double matchFitness1 = computeMatchFitness(i1->first, u1, i4->first, u4);
611 double matchFitness2 = computeMatchFitness(i2->first, u2, i4->first, u4);
612 double matchFitness3 = computeMatchFitness(i3->first, u3, i4->first, u4);
613 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
614 tmp = MapSetUnion(tmp, u3->maps);
615 tmp = MapSetUnion(tmp, u4->maps);
616 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold && matchFitness3 > fitnessThreshold)
617 {
618 if (numPlayers == 4)
619 {
620 // match 4 players
621 sendMatchInfo(i1->first, i2->first, i3->first, i4->first, "", "", "", "",
622 u1, u2, u3, u4, NULL, NULL, NULL, NULL, 4, ladderID);
623 break;
624 }
625 else
626 {
627
628 i5 = i4;
629 for (++i5; i5 != userMap.end(); ++i3)
630 {
631 u5 = i5->second;
632 if (u5->status != STATUS_WORKING)
633 continue;
634
635 double matchFitness1 = computeMatchFitness(i1->first, u1, i5->first, u5);
636 double matchFitness2 = computeMatchFitness(i2->first, u2, i5->first, u5);
637 double matchFitness3 = computeMatchFitness(i3->first, u3, i5->first, u5);
638 double matchFitness4 = computeMatchFitness(i4->first, u4, i5->first, u5);
639 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
640 tmp = MapSetUnion(tmp, u3->maps);
641 tmp = MapSetUnion(tmp, u4->maps);
642 tmp = MapSetUnion(tmp, u5->maps);
643 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold && matchFitness3 > fitnessThreshold && matchFitness4 > fitnessThreshold)
644 {
645 i6 = i5;
646 for (++i6; i6 != userMap.end(); ++i6)
647 {
648 u6 = i6->second;
649 if (u6->status != STATUS_WORKING)
650 continue;
651
652 double matchFitness1 = computeMatchFitness(i1->first, u1, i6->first, u6);
653 double matchFitness2 = computeMatchFitness(i2->first, u2, i6->first, u6);
654 double matchFitness3 = computeMatchFitness(i3->first, u3, i6->first, u6);
655 double matchFitness4 = computeMatchFitness(i4->first, u4, i6->first, u6);
656 double matchFitness5 = computeMatchFitness(i5->first, u5, i6->first, u6);
657 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
658 tmp = MapSetUnion(tmp, u3->maps);
659 tmp = MapSetUnion(tmp, u4->maps);
660 tmp = MapSetUnion(tmp, u5->maps);
661 tmp = MapSetUnion(tmp, u6->maps);
662 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold && matchFitness3 > fitnessThreshold && matchFitness4 > fitnessThreshold && matchFitness5 > fitnessThreshold)
663 {
664 if (numPlayers == 6)
665 {
666 // match 6 players
667 sendMatchInfo(i1->first, i2->first, i3->first, i4->first, i5->first, i6->first, "", "",
668 u1, u2, u3, u4, u5, u6, NULL, NULL, 6, ladderID);
669 break;
670 }
671 else
672 {
673
674 i7 = i6;
675 for (++i7; i7 != userMap.end(); ++i7)
676 {
677 u7 = i7->second;
678 if (u7->status != STATUS_WORKING)
679 continue;
680
681 double matchFitness1 = computeMatchFitness(i1->first, u1, i7->first, u7);
682 double matchFitness2 = computeMatchFitness(i2->first, u2, i7->first, u7);
683 double matchFitness3 = computeMatchFitness(i3->first, u3, i7->first, u7);
684 double matchFitness4 = computeMatchFitness(i4->first, u4, i7->first, u7);
685 double matchFitness5 = computeMatchFitness(i5->first, u5, i7->first, u7);
686 double matchFitness6 = computeMatchFitness(i6->first, u6, i7->first, u7);
687 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
688 tmp = MapSetUnion(tmp, u3->maps);
689 tmp = MapSetUnion(tmp, u4->maps);
690 tmp = MapSetUnion(tmp, u5->maps);
691 tmp = MapSetUnion(tmp, u6->maps);
692 tmp = MapSetUnion(tmp, u7->maps);
693 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold && matchFitness3 > fitnessThreshold && matchFitness4 > fitnessThreshold && matchFitness5 > fitnessThreshold && matchFitness6 > fitnessThreshold)
694 {
695 i8 = i7;
696 for (++i8; i8 != userMap.end(); ++i8)
697 {
698 u8 = i8->second;
699 if (u8->status != STATUS_WORKING)
700 continue;
701
702 double matchFitness1 = computeMatchFitness(i1->first, u1, i8->first, u8);
703 double matchFitness2 = computeMatchFitness(i2->first, u2, i8->first, u8);
704 double matchFitness3 = computeMatchFitness(i3->first, u3, i8->first, u8);
705 double matchFitness4 = computeMatchFitness(i4->first, u4, i8->first, u8);
706 double matchFitness5 = computeMatchFitness(i5->first, u5, i8->first, u8);
707 double matchFitness6 = computeMatchFitness(i6->first, u6, i8->first, u8);
708 double matchFitness7 = computeMatchFitness(i7->first, u7, i8->first, u8);
709 MapBitSet tmp = MapSetUnion(u1->maps, u2->maps);
710 tmp = MapSetUnion(tmp, u3->maps);
711 tmp = MapSetUnion(tmp, u4->maps);
712 tmp = MapSetUnion(tmp, u5->maps);
713 tmp = MapSetUnion(tmp, u6->maps);
714 tmp = MapSetUnion(tmp, u7->maps);
715 tmp = MapSetUnion(tmp, u8->maps);
716 if (MapSetCount(tmp) && matchFitness1 > fitnessThreshold && matchFitness2 > fitnessThreshold && matchFitness3 > fitnessThreshold && matchFitness4 > fitnessThreshold && matchFitness5 > fitnessThreshold && matchFitness6 > fitnessThreshold && matchFitness7 > fitnessThreshold)
717 {
718 // match 8 players
719 sendMatchInfo(i1->first, i2->first, i3->first, i4->first, i5->first, i6->first, i7->first, i8->first,
720 u1, u2, u3, u4, u5, u6, u7, u8, 8, ladderID);
721 break;
722 }
723 }
724 }
725 }
726
727 }
728 }
729 }
730 }
731 }
732
733 }
734 }
735 }
736 }
737 }
738 }
739 }
740 } // for i2
741
742 if (bestUser && numPlayers == 2)
743 {
744 // we had a match. send the info.
745 DBGMSG("Matching " << i1->first << " with " << bestName << ":\n"
746 "\tmatch fitness: " << bestMatchFitness << "\n"
747 "\tpoint percentage: " << (1-bestUser->points/(double)u1->points)*100 << "\n"
748 "\tpoints: " << u1->points << ", " << u2->points << "\n"
749 "\tping in ms: " << sqrt(1000000 * calcPingDelta(u1, bestUser) / (255*255*2)) << "\n"
750 "\tprevious attempts: " << u1->widened << ", " << bestUser->widened);
751 sendMatchInfo(i1->first, bestName, "", "", "", "", "", "",
752 u1, bestUser, NULL, NULL, NULL, NULL, NULL, NULL, 2, ladderID);
753 break;
754 }
755 } // for i1
756
757 dumpUsers();
758}
759
760// return false for possible hack attempt
761bool GeneralsMatcher::handleUserWiden(const char *nick)
762{
763 GeneralsUser *userInfo = findUserInAnyLadder(nick);
764 if (!userInfo)
765 {
766 userInfo = findNonLadderUser(nick);
767 }
768
769 if (!userInfo)
770 {
771 DBGMSG("Got Widen from nick not needing one!");
772 peerMessagePlayer(m_peer, nick, "MBOT:CANTSENDWIDENNOW", NormalMessage);
773 return false;
774 }
775 DBGMSG("Widening search for " << nick);
776 peerMessagePlayer(m_peer, nick, "MBOT:WIDENINGSEARCH", NormalMessage);
777
778 userInfo->widened = true;
779 return true;
780}
781
782bool GeneralsMatcher::handleUserInfo(const char *nick, const std::string& msg)
783{
784 DBGMSG("Got user info [" << msg << "] from " << nick);
785 GeneralsUser *userInfo = removeNonMatchingUser(nick);
786 if (!userInfo)
787 {
788 DBGMSG("Got UserInfo from nick not needing one!");
789 peerMessagePlayer(m_peer, nick, "MBOT:CANTSENDCINFONOW", NormalMessage);
790 return false;
791 }
792 DBGMSG("Looking at " << nick << " with user info [" << msg << "]");
793
794 int ladderID = 0;
795 unsigned int ladderPassCRC = 0;
796
797 int offset = 0;
798 while (1)
799 {
800 int firstMarker = msg.find_first_of('\\', offset);
801 if (firstMarker < 0)
802 break;
803 int secondMarker = msg.find_first_of('\\', firstMarker + 1);
804 if (secondMarker < 0)
805 break;
806 int thirdMarker = msg.find_first_of('\\', secondMarker + 1);
807 if (thirdMarker < 0)
808 break;
809 std::string k = msg.substr(firstMarker + 1, secondMarker - firstMarker - 1);
810 std::string v = msg.substr(secondMarker + 1, thirdMarker - secondMarker - 1);
811 offset = thirdMarker - 1;
812
813 if (k == "Widen")
814 {
815 int val = atoi(v.c_str());
816 if (val > 0)
817 userInfo->timeToWiden = time(NULL) + val;
818 else
819 userInfo->timeToWiden = 0;
820 }
821 else if (k == "LadID")
822 {
823 ladderID = atoi(v.c_str());
824 }
825 else if (k == "LadPass")
826 {
827 ladderPassCRC = atoi(v.c_str());
828 }
829 else if (k == "PointsMin")
830 {
831 userInfo->minPoints = atoi(v.c_str());
832 }
833 else if (k == "PointsMax")
834 {
835 userInfo->maxPoints = atoi(v.c_str());
836 }
837 else if (k == "PingMax")
838 {
839 userInfo->maxPing = atoi(v.c_str());
840 }
841 else if (k == "DisconMax")
842 {
843 userInfo->maxDiscons = atoi(v.c_str());
844 }
845 else if (k == "Maps")
846 {
847#ifdef DEBUG
848 //int curMaps = userInfo->maps.size();
849#endif
850
851 //DBGMSG("map cur size is " << curMaps);
852 userInfo->maps.clear();
853 if (!v.length())
854 {
855 INFMSG("Bad maps from " << nick << ": [" << v << "]");
856 peerMessagePlayer(m_peer, nick, "MBOT:BADMAPS", NormalMessage);
857 return false;
858 }
859 const char *buf = v.c_str();
860 int pos = 0;
861 while (*buf)
862 {
863 bool hasMap = (*buf != '0');
864 //DBGMSG("Setting map " << pos << " to " << hasMap);
865 userInfo->maps.push_back( hasMap );
866 ++pos;
867 ++buf;
868 }
869 }
870 else if (k == "NumPlayers")
871 {
872 userInfo->numPlayers = atoi(v.c_str());
873 if (userInfo->numPlayers != 2 && userInfo->numPlayers != 4 &&
874 userInfo->numPlayers != 6 && userInfo->numPlayers != 8)
875 {
876 INFMSG("Bad numPlayers from " << nick << ": [" << userInfo->numPlayers << "]");
877 peerMessagePlayer(m_peer, nick, "MBOT:BADCINFO", NormalMessage);
878 return false;
879 }
880 }
881 else if (k == "IP")
882 {
883 userInfo->IP = atoi(v.c_str());
884 }
885 else if (k == "NAT")
886 {
887 userInfo->NAT = atoi(v.c_str());
888 }
889 else if (k == "Side")
890 {
891 userInfo->country = atoi(v.c_str());
892 }
893 else if (k == "Color")
894 {
895 userInfo->color = atoi(v.c_str());
896 }
897 else if (k == "Pings")
898 {
899 if (!v.length() || (v.length() % 2))
900 {
901 INFMSG("Bad pings from " << nick << ": [" << v << "]");
902 peerMessagePlayer(m_peer, nick, "MBOT:BADPINGS", NormalMessage);
903 return false;
904 }
905 int ping = 0;
906 const char *buf = v.c_str();
907 char buf2[3];
908 buf2[2] = '\0';
909 // We've already assured that pingStr has non-zero even length.
910 while (*buf)
911 {
912 buf2[0] = *buf++;
913 buf2[1] = *buf++;
914 ping = (int)strtol(buf2, NULL, 16);
915 userInfo->pseudoPing.push_back(ping);
916 }
917 }
918 else if (k == "Points")
919 {
920 userInfo->points = max(1, atoi(v.c_str()));
921 }
922 else if (k == "Discons")
923 {
924 userInfo->discons = atoi(v.c_str());
925 }
926 else
927 {
928 INFMSG("Unknown key/value pair in user info [" << k << "]/[" << v << "]");
929 peerMessagePlayer(m_peer, nick, "MBOT:BADCINFO", NormalMessage);
930 return false;
931 }
932 }
933
934 std::string s = "MBOT:WORKING ";
935
936 if (ladderID)
937 {
938 addUserInLadder(nick, ladderID, userInfo);
939 s.append(intToString(m_ladders[ladderID].size()));
940 }
941 else
942 {
943 addNonLadderUser(nick, userInfo);
944 switch (userInfo->numPlayers)
945 {
946 case 2:
947 s.append(intToString(m_nonLadderUsers1v1.size()));
948 break;
949 case 4:
950 s.append(intToString(m_nonLadderUsers2v2.size()));
951 break;
952 case 6:
953 s.append(intToString(m_nonLadderUsers3v3.size()));
954 break;
955 case 8:
956 s.append(intToString(m_nonLadderUsers4v4.size()));
957 break;
958 }
959 }
960
961 userInfo->status = STATUS_WORKING;
962 userInfo->matchStart = time(NULL);
963 peerMessagePlayer(m_peer, nick, s.c_str(), NormalMessage);
964
965 DBGMSG("Player " << nick << " is matching now, ack was [" << s << "]");
966 return true;
967}
968
969GeneralsUser* GeneralsMatcher::findUser(const std::string& who)
970{
971 GeneralsUser *user;
972 user = findNonLadderUser(who);
973 if (user)
974 return user;
975 user = findNonMatchingUser(who);
976 if (user)
977 return user;
978 user = findUserInAnyLadder(who);
979 if (user)
980 return user;
981
982 return NULL;
983}
984
985GeneralsUser* GeneralsMatcher::findUserInAnyLadder(const std::string& who)
986{
987 for (LadderMap::iterator lIt = m_ladders.begin(); lIt != m_ladders.end(); ++lIt)
988 {
989 UserMap::iterator uIt = lIt->second.find(who);
990 if (uIt != lIt->second.end())
991 return uIt->second;
992 }
993 return NULL;
994}
995
996GeneralsUser* GeneralsMatcher::findUserInLadder(const std::string& who, int ladderID)
997{
998 LadderMap::iterator lIt = m_ladders.find(ladderID);
999 if (lIt == m_ladders.end())
1000 return NULL;
1001
1002 UserMap::iterator uIt = lIt->second.find(who);
1003 if (uIt == lIt->second.end())
1004 return NULL;
1005
1006 return uIt->second;
1007}
1008
1009GeneralsUser* GeneralsMatcher::findNonLadderUser(const std::string& who)
1010{
1011 UserMap::iterator it = m_nonLadderUsers1v1.find(who);
1012 if (it != m_nonLadderUsers1v1.end())
1013 return it->second;
1014
1015 it = m_nonLadderUsers2v2.find(who);
1016 if (it != m_nonLadderUsers2v2.end())
1017 return it->second;
1018
1019 it = m_nonLadderUsers3v3.find(who);
1020 if (it != m_nonLadderUsers3v3.end())
1021 return it->second;
1022
1023 it = m_nonLadderUsers4v4.find(who);
1024 if (it != m_nonLadderUsers4v4.end())
1025 return it->second;
1026
1027 return NULL;
1028}
1029
1030GeneralsUser* GeneralsMatcher::findNonMatchingUser(const std::string& who)
1031{
1032 UserMap::iterator it = m_nonMatchingUsers.find(who);
1033 if (it == m_nonMatchingUsers.end())
1034 return NULL;
1035
1036 return it->second;
1037}
1038
1039void GeneralsMatcher::addUser(const std::string& who)
1040{
1041 if (findUser(who))
1042 {
1043 ERRMSG("Re-adding " << who);
1044 return;
1045 }
1046
1047 addNonMatchingUser(who, new GeneralsUser);
1048}
1049
1050void GeneralsMatcher::addUserInLadder(const std::string& who, int ladderID, GeneralsUser *user)
1051{
1052 m_ladders[ladderID][who] = user;
1053}
1054
1055void GeneralsMatcher::addNonLadderUser(const std::string& who, GeneralsUser *user)
1056{
1057 switch (user->numPlayers)
1058 {
1059 case 2:
1060 m_nonLadderUsers1v1[who] = user;
1061 break;
1062 case 4:
1063 m_nonLadderUsers2v2[who] = user;
1064 break;
1065 case 6:
1066 m_nonLadderUsers3v3[who] = user;
1067 break;
1068 case 8:
1069 m_nonLadderUsers4v4[who] = user;
1070 break;
1071 }
1072}
1073
1074void GeneralsMatcher::addNonMatchingUser(const std::string& who, GeneralsUser *user)
1075{
1076 m_nonMatchingUsers[who] = user;
1077}
1078
1079
1080bool GeneralsMatcher::removeUser(const std::string& who)
1081{
1082 GeneralsUser *user;
1083 user = removeUserInAnyLadder(who);
1084 if (user)
1085 {
1086 delete user;
1087 return true;
1088 }
1089 user = removeNonLadderUser(who);
1090 if (user)
1091 {
1092 delete user;
1093 return true;
1094 }
1095 user = removeNonMatchingUser(who);
1096 if (user)
1097 {
1098 delete user;
1099 return true;
1100 }
1101
1102 return false;
1103}
1104
1105GeneralsUser* GeneralsMatcher::removeUserInLadder(const std::string& who, int ladderID)
1106{
1107 LadderMap::iterator lIt = m_ladders.find(ladderID);
1108 if (lIt == m_ladders.end())
1109 return NULL;
1110
1111 UserMap::iterator uIt = lIt->second.find(who);
1112 if (uIt == lIt->second.end())
1113 return NULL;
1114
1115 GeneralsUser *user = uIt->second;
1116 lIt->second.erase(uIt);
1117 return user;
1118}
1119
1120GeneralsUser* GeneralsMatcher::removeUserInAnyLadder(const std::string& who)
1121{
1122 for (LadderMap::iterator lIt = m_ladders.begin(); lIt != m_ladders.end(); ++lIt)
1123 {
1124 UserMap::iterator uIt = lIt->second.find(who);
1125 if (uIt != lIt->second.end())
1126 {
1127 GeneralsUser *user = uIt->second;
1128 lIt->second.erase(uIt);
1129 return user;
1130 }
1131 }
1132 return NULL;
1133}
1134
1135GeneralsUser* GeneralsMatcher::removeNonLadderUser(const std::string& who)
1136{
1137 UserMap::iterator it = m_nonLadderUsers1v1.find(who);
1138 if (it != m_nonLadderUsers1v1.end())
1139 {
1140 GeneralsUser *user = it->second;
1141 m_nonLadderUsers1v1.erase(it);
1142 return user;
1143 }
1144
1145 it = m_nonLadderUsers2v2.find(who);
1146 if (it != m_nonLadderUsers2v2.end())
1147 {
1148 GeneralsUser *user = it->second;
1149 m_nonLadderUsers2v2.erase(it);
1150 return user;
1151 }
1152
1153 it = m_nonLadderUsers3v3.find(who);
1154 if (it != m_nonLadderUsers3v3.end())
1155 {
1156 GeneralsUser *user = it->second;
1157 m_nonLadderUsers3v3.erase(it);
1158 return user;
1159 }
1160
1161 it = m_nonLadderUsers4v4.find(who);
1162 if (it != m_nonLadderUsers4v4.end())
1163 {
1164 GeneralsUser *user = it->second;
1165 m_nonLadderUsers4v4.erase(it);
1166 return user;
1167 }
1168
1169 return NULL;
1170}
1171
1172GeneralsUser* GeneralsMatcher::removeNonMatchingUser(const std::string& who)
1173{
1174 UserMap::iterator it = m_nonMatchingUsers.find(who);
1175 if (it == m_nonMatchingUsers.end())
1176 return NULL;
1177
1178 GeneralsUser *user = it->second;
1179 m_nonMatchingUsers.erase(it);
1180 return user;
1181}
1182
1183
1184void GeneralsMatcher::handleDisconnect( const char *reason )
1185{
1186 ERRMSG("Disconnected");
1187 done = true;
1188 exit(0);
1189}
1190
1191void GeneralsMatcher::handleRoomMessage( const char *nick, const char *message, MessageType messageType )
1192{
1193 if (messageType == ActionMessage)
1194 {
1195 PARANOIDMSG(nick << " " << message);
1196 }
1197 else
1198 {
1199 PARANOIDMSG("[" << nick << "] " << message);
1200 }
1201}
1202
1203void GeneralsMatcher::handlePlayerMessage( const char *nick, const char *message, MessageType messageType )
1204{
1205 if (messageType == ActionMessage)
1206 {
1207 DBGMSG(nick << " " << message);
1208 }
1209 else
1210 {
1211 DBGMSG("[" << nick << "] " << message);
1212 }
1213
1214 if (messageType != NormalMessage)
1215 return;
1216
1217 std::string line = message;
1218 line.append("\\");
1219
1220 int offset = 0;
1221 int firstMarker = line.find_first_of('\\', offset);
1222 int secondMarker = line.find_first_of('\\', firstMarker + 1);
1223 if (firstMarker >= 0 && secondMarker >= 0)
1224 {
1225 std::string marker = line.substr(firstMarker + 1, secondMarker - firstMarker - 1);
1226 if (marker == "CINFO")
1227 {
1228 handleUserInfo(nick, line.substr(secondMarker));//, std::string::npos));
1229 }
1230 else if (marker == "WIDEN")
1231 {
1232 handleUserWiden(nick);
1233 }
1234 else
1235 {
1236 INFMSG("Unknown marker [" << marker << "] in line [" << line << "] from " << nick);
1237 }
1238 }
1239 else
1240 {
1241 INFMSG("Failed to parse line [" << line << "] from " << nick);
1242 }
1243}
1244
1246{
1247 DBGMSG("Player " << nick << " joined");
1248 addUser(nick);
1249}
1250
1252{
1253 DBGMSG("Player " << nick << " left");
1254 if (m_nick != nick)
1255 removeUser(nick);
1256}
1257
1258void GeneralsMatcher::handlePlayerChangedNick( const char *oldNick, const char *newNick )
1259{
1260 DBGMSG("Player " << oldNick << " changed nick to " << newNick << " - resetting to non-matching state");
1261 removeUser(oldNick);
1262 addUser(newNick);
1263}
1264
1265void GeneralsMatcher::handlePlayerEnum( bool success, int gameSpyIndex, const char *nick, int flags )
1266{
1267 if (!nick)
1268 nick = "";
1269 DBGMSG("PlayerEnum: success=" << success << " index=" << gameSpyIndex << ", nick=" << nick << ", flags=" << flags);
1270
1271 if (success && gameSpyIndex >= 0 && m_nick != nick)
1272 {
1273 addUser(nick);
1274 }
1275}
1276
1277
1278// =====================================================================
1279// TEST Client Matcher class
1280// =====================================================================
1281
1283{
1284 // Read some values from the config file
1285 int quietTMP = 0;
1286 Global.config.getInt("NOECHO", quietTMP);
1287 if (quietTMP)
1288 quiet = true;
1289 else
1290 quiet = false;
1291}
1292
1294{
1295 m_baseNick.setFormatted("qmBot%d", time(NULL));
1296 m_profileID = 0;
1297}
1298
1301
1303{}
1304void GeneralsClientMatcher::handleRoomMessage( const char *nick, const char *message, MessageType messageType )
1305{}
1306void GeneralsClientMatcher::handlePlayerMessage( const char *nick, const char *message, MessageType messageType )
1307{}
1309{}
1311{}
1312void GeneralsClientMatcher::handlePlayerChangedNick( const char *oldNick, const char *newNick )
1313{}
1314void GeneralsClientMatcher::handlePlayerEnum( bool success, int gameSpyIndex, const char *nick, int flags )
1315{}
1316
1317
1318// =====================================================================
1319// End of File
1320// =====================================================================
1321
1322
#define NULL
Definition BaseType.h:92
#define min(x, y)
Definition BaseType.h:101
#define max(x, y)
Definition BaseType.h:105
std::string intToString(int val)
Definition KVPair.cpp:31
#define INFMSG(X)
Definition wdebug.h:64
#define ERRMSG(X)
Definition wdebug.h:86
#define DBGMSG(X)
Definition wdebug.h:128
virtual void handlePlayerEnum(bool success, int gameSpyIndex, const char *nick, int flags)
virtual void handlePlayerChangedNick(const char *oldNick, const char *newNick)
virtual void checkMatches(void)
virtual void handlePlayerJoined(const char *nick)
virtual void handlePlayerLeft(const char *nick)
virtual void handleDisconnect(const char *reason)
virtual void handleRoomMessage(const char *nick, const char *message, MessageType messageType)
virtual void handlePlayerMessage(const char *nick, const char *message, MessageType messageType)
virtual void init(void)
virtual void handleDisconnect(const char *reason)
virtual void handlePlayerLeft(const char *nick)
virtual void handlePlayerMessage(const char *nick, const char *message, MessageType messageType)
virtual void handlePlayerJoined(const char *nick)
virtual void checkMatches(void)
Definition generals.cpp:434
virtual void handlePlayerEnum(bool success, int gameSpyIndex, const char *nick, int flags)
virtual void init(void)
Definition generals.cpp:200
virtual void handlePlayerChangedNick(const char *oldNick, const char *newNick)
virtual void handleRoomMessage(const char *nick, const char *message, MessageType messageType)
std::vector< int > pseudoPing
Definition generals.h:74
int maxPoints
Definition generals.h:57
UserStatus status
Definition generals.h:55
int maxDiscons
Definition generals.h:59
bool widened
Definition generals.h:64
GeneralsUser(void)
Definition generals.cpp:131
time_t timeToWiden
Definition generals.h:65
int minPoints
Definition generals.h:57
MapBitSet maps
Definition generals.h:80
unsigned int IP
Definition generals.h:77
time_t matchStart
Definition generals.h:66
int numPlayers
Definition generals.h:82
RandClass rnd
Definition global.h:47
int m_profileID
Definition matcher.h:77
PEER m_peer
Definition matcher.h:78
bool quiet
Definition matcher.h:84
Wstring m_baseNick
Definition matcher.h:75
std::string m_nick
Definition matcher.h:76
int Int(void)
Definition rand.cpp:97
MapBitSet MapSetUnion(const MapBitSet &a, const MapBitSet &b)
Definition generals.cpp:101
std::string uintToString(unsigned int val)
Definition generals.cpp:84
int calcPingDelta(const GeneralsUser *a, const GeneralsUser *b)
Definition generals.cpp:149
int MapSetCount(const MapBitSet &a)
Definition generals.cpp:115
std::string intToString(int val)
Definition generals.cpp:60
std::map< std::string, GeneralsUser * > UserMap
Definition generals.h:89
@ STATUS_WORKING
Definition generals.h:47
@ STATUS_INCHANNEL
Definition generals.h:46
@ STATUS_MATCHED
Definition generals.h:48
std::vector< bool > MapBitSet
Definition generals.h:36
GlobalClass Global
Definition global.cpp:22
#define PARANOIDMSG(X)
Definition mydebug.h:97
MSG msg
Definition patch.cpp:409
long time_t
Definition wolapi.h:436