generals.cpp 34 KB

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