1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace ManiaLive\Data;
13:
14: use ManiaLib\Utils\Formatting;
15: use ManiaLive\Event\Dispatcher;
16: use ManiaLive\Application\Listener as AppListener;
17: use ManiaLive\Application\Event as AppEvent;
18: use ManiaLive\DedicatedApi\Callback\Listener as ServerListener;
19: use ManiaLive\DedicatedApi\Callback\Event as ServerEvent;
20: use DedicatedApi\Connection;
21: use DedicatedApi\Structures\GameInfos;
22: use DedicatedApi\Structures\Map;
23: use DedicatedApi\Structures\Player;
24: use DedicatedApi\Structures\Vote;
25: use ManiaLive\Utilities\Console;
26: use ManiaLive\Application\SilentCriticalEventException;
27: use ManiaLive\Application\CriticalEventException;
28:
29: 30: 31:
32: class Storage extends \ManiaLib\Utils\Singleton implements ServerListener, AppListener
33: {
34:
35: 36: 37: 38:
39: private $connection;
40: private $disconnectedPlayers = array();
41:
42: 43: 44:
45: private $checkpoints = array();
46:
47: 48: 49: 50:
51: public $players = array();
52:
53: 54: 55: 56:
57: public $spectators = array();
58:
59: 60: 61: 62:
63: public $maps;
64:
65: 66: 67: 68:
69: public $currentMap;
70:
71: 72: 73: 74:
75: public $nextMap;
76:
77: 78: 79: 80:
81: public $server;
82:
83: 84: 85: 86:
87: public $gameInfos;
88:
89: 90: 91: 92:
93: public $serverStatus;
94:
95: 96: 97: 98:
99: public $serverLogin;
100:
101: 102: 103: 104:
105: public $currentVote;
106: private $isWarmUp = false;
107:
108: protected function __construct()
109: {
110: Dispatcher::register(AppEvent::getClass(), $this, AppEvent::ON_INIT | AppEvent::ON_POST_LOOP);
111: Dispatcher::register(ServerEvent::getClass(), $this, ServerEvent::ALL);
112: }
113:
114:
115:
116: function onInit()
117: {
118: $config = \ManiaLive\DedicatedApi\Config::getInstance();
119: $this->connection = Connection::factory($config->host, $config->port, $config->timeout, $config->user, $config->password);
120: $this->serverStatus = $this->connection->getStatus();
121:
122: $players = $this->connection->getPlayerList(-1, 0);
123: foreach($players as $player)
124: {
125: try
126: {
127: $details = $this->connection->getDetailedPlayerInfo($player->login);
128:
129: foreach($details as $key => $value)
130: if($value) $player->$key = $value;
131:
132: if($player->spectatorStatus % 10 == 0) $this->players[$player->login] = $player;
133: else $this->spectators[$player->login] = $player;
134: }
135: catch(\Exception $e)
136: {
137:
138: }
139: }
140:
141: try
142: {
143: $this->maps = $this->connection->getMapList(-1, 0);
144: $this->nextMap = $this->maps[$this->connection->getNextMapIndex()];
145: $this->currentMap = $this->connection->getCurrentMapInfo();
146: Console::printlnFormatted('Current map: '.Formatting::stripStyles($this->currentMap->name));
147: }
148: catch(\Exception $e)
149: {
150: $this->maps = array();
151: $this->nextMap = null;
152: $this->currentMap = null;
153: }
154: $this->server = $this->connection->getServerOptions();
155: $this->gameInfos = $this->connection->getCurrentGameInfo();
156: try
157: {
158: $this->serverLogin = $this->connection->getSystemInfo()->serverLogin;
159: }
160: catch(\Exception $e)
161: {
162: $this->serverLogin = null;
163: }
164: }
165:
166: function onPostLoop()
167: {
168: foreach($this->disconnectedPlayers as $login)
169: {
170: if(isset($this->spectators[$login]) && !$this->spectators[$login]->isConnected) unset($this->spectators[$login]);
171: else if(isset($this->players[$login]) && !$this->players[$login]->isConnected) unset($this->players[$login]);
172: }
173: $this->disconnectedPlayers = array();
174:
175: if($this->currentVote instanceof Vote && $this->currentVote->status != Vote::STATE_NEW) $this->currentVote = null;
176: }
177:
178: function onRun()
179: {
180:
181: }
182:
183: function onPreLoop()
184: {
185:
186: }
187:
188: function onTerminate()
189: {
190:
191: }
192:
193:
194:
195:
196: function onPlayerConnect($login, $isSpectator)
197: {
198: try
199: {
200: $playerInfos = $this->connection->getPlayerInfo($login, 1);
201: $details = $this->connection->getDetailedPlayerInfo($login);
202:
203: foreach($details as $key => $value)
204: if($value) $playerInfos->$key = $value;
205:
206: if($isSpectator) $this->spectators[$login] = $playerInfos;
207: else $this->players[$login] = $playerInfos;
208: }
209:
210:
211: catch(\Exception $e)
212: {
213: if($e->getCode() == -1000 && $e->getMessage() == 'Login unknown.')
214: throw new SilentCriticalEventException($e->getMessage());
215: else throw new CriticalEventException($e->getMessage());
216: }
217: }
218:
219: function onPlayerDisconnect($login)
220: {
221: $this->disconnectedPlayers[] = $login;
222:
223: if(isset($this->players[$login])) $this->players[$login]->isConnected = false;
224: else if(isset($this->spectators[$login])) $this->spectators[$login]->isConnected = false;
225: }
226:
227: function onPlayerChat($playerUid, $login, $text, $isRegistredCmd)
228: {
229:
230: }
231:
232: function onPlayerManialinkPageAnswer($playerUid, $login, $answer, array $entries)
233: {
234:
235: }
236:
237: function onEcho($internal, $public)
238: {
239:
240: }
241:
242: function onServerStart()
243: {
244: try
245: {
246: $this->serverLogin = $this->connection->getMainServerPlayerInfo()->login;
247: $this->maps = $this->connection->getMapList(-1, 0);
248: }
249: catch(\Exception $e)
250: {
251: $this->serverLogin = null;
252: $this->maps = array();
253: }
254: }
255:
256: function onServerStop()
257: {
258:
259: }
260:
261: function onBeginMatch()
262: {
263:
264: }
265:
266: function onEndMatch($rankings, $winnerTeamOrMap)
267: {
268: if($this->isWarmUp && $this->gameInfos->gameMode == GameInfos::GAMEMODE_LAPS)
269: {
270: $this->resetScores();
271: $this->isWarmUp = false;
272: }
273: else $this->updateRanking(Player::fromArrayOfArray($rankings));
274: }
275:
276: function onBeginMap($map, $warmUp, $matchContinuation)
277: {
278: $this->checkpoints = array();
279:
280: $oldMap = $this->currentMap;
281: $this->currentMap = Map::fromArray($map);
282: if($oldMap)
283: {
284: Console::printlnFormatted('Map change: '.Formatting::stripStyles($oldMap->name).' -> '.Formatting::stripStyles($this->currentMap->name));
285: }
286: $this->resetScores();
287:
288: if($warmUp) $this->isWarmUp = true;
289:
290: $gameInfos = $this->connection->getCurrentGameInfo();
291: if($gameInfos != $this->gameInfos) foreach($gameInfos as $key => $value)
292: $this->gameInfos->$key = $value;
293:
294: $serverOptions = $this->connection->getServerOptions();
295: if($serverOptions != $this->server) foreach($serverOptions as $key => $value)
296: $this->server->$key = $value;
297: }
298:
299: function onEndMap($rankings, $map, $wasWarmUp, $matchContinuesOnNextMap, $restartMap)
300: {
301: if(!$wasWarmUp)
302: {
303: $rankings = Player::fromArrayOfArray($rankings);
304: $this->updateRanking($rankings);
305: }
306: else
307: {
308: $this->resetScores();
309: $this->isWarmUp = false;
310: }
311: }
312:
313: function onBeginRound()
314: {
315:
316: }
317:
318: function onEndRound()
319: {
320:
321: try
322: {
323: if(count($this->players) || count($this->spectators))
324: $this->updateRanking($this->connection->getCurrentRanking(-1, 0));
325: }
326: catch(\Exception $ex)
327: {
328:
329: }
330: }
331:
332: function onStatusChanged($statusCode, $statusName)
333: {
334: $this->serverStatus->code = $statusCode;
335: $this->serverStatus->name = $statusName;
336: }
337:
338: function getLapCheckpoints($player)
339: {
340: $login = $player->login;
341: if(isset($this->checkpoints[$login]))
342: {
343: $checkCount = count($this->checkpoints[$login]) - 1;
344: $offset = ($checkCount % $this->currentMap->nbCheckpoints) + 1;
345: $checks = array_slice($this->checkpoints[$login], -$offset);
346:
347: if($checkCount >= $this->currentMap->nbCheckpoints)
348: {
349: $timeOffset = $this->checkpoints[$login][$checkCount - $offset];
350:
351: for($i = 0; $i < count($checks); ++$i)
352: $checks[$i] -= $timeOffset;
353: }
354:
355: return $checks;
356: }
357: else return array();
358: }
359:
360: function onPlayerCheckpoint($playerUid, $login, $timeOrScore, $curLap, $checkpointIndex)
361: {
362:
363: if($checkpointIndex == 0) $this->checkpoints[$login] = array();
364:
365: else if($checkpointIndex > 0 &&
366: (!isset($this->checkpoints[$login])
367: || !isset($this->checkpoints[$login][$checkpointIndex - 1])
368: || $timeOrScore < $this->checkpoints[$login][$checkpointIndex - 1])) return;
369:
370:
371: $this->checkpoints[$login][$checkpointIndex] = $timeOrScore;
372:
373:
374: if($this->currentMap->nbCheckpoints && ($checkpointIndex + 1) % $this->currentMap->nbCheckpoints == 0)
375: {
376: $player = $this->getPlayerObject($login);
377: if($player)
378: {
379:
380: $checkpoints = array_slice($this->checkpoints[$login], -$this->currentMap->nbCheckpoints);
381:
382:
383: if($checkpointIndex >= $this->currentMap->nbCheckpoints)
384: {
385:
386: $offset = $this->checkpoints[$login][($checkpointIndex - $this->currentMap->nbCheckpoints)];
387: for($i = 0; $i < count($checkpoints); ++$i)
388: $checkpoints[$i] -= $offset;
389:
390:
391: $timeOrScore -= $offset;
392: }
393:
394:
395: if(end($checkpoints) != $timeOrScore) return;
396:
397:
398: Dispatcher::dispatch(new Event(Event::ON_PLAYER_FINISH_LAP, $player, end($checkpoints), $checkpoints, $curLap));
399: }
400: }
401: }
402:
403: function onPlayerFinish($playerUid, $login, $timeOrScore)
404: {
405: if(!isset($this->players[$login])) return;
406: $player = $this->players[$login];
407:
408: switch($this->gameInfos->gameMode)
409: {
410:
411: case GameInfos::GAMEMODE_STUNTS:
412: if($timeOrScore > 0 && ($player->score <= 0 || $timeOrScore > $player->score))
413: {
414: $oldScore = $player->score;
415: $this->updateRanking($this->connection->getCurrentRanking(-1, 0));
416:
417: if($player->score == $timeOrScore)
418: {
419:
420: if(count($player->bestCheckpoints) != $this->currentMap->nbCheckpoints)
421: {
422: Console::println('Best score\'s checkpoint count does not match and was ignored!');
423: Console::printPlayerScore($player);
424: $player->score = $oldScore;
425: return;
426: }
427:
428: Dispatcher::dispatch(new Event(Event::ON_PLAYER_NEW_BEST_SCORE, $player, $oldScore, $timeOrScore));
429: }
430: }
431: break;
432:
433:
434: default:
435: if($timeOrScore > 0 && ($player->bestTime <= 0 || $timeOrScore < $player->bestTime))
436: {
437: $oldBest = $player->bestTime;
438: $this->updateRanking($this->connection->getCurrentRanking(-1, 0));
439:
440: if($player->bestTime == $timeOrScore)
441: {
442:
443: $totalChecks = 0;
444: switch($this->gameInfos->gameMode)
445: {
446: case GameInfos::GAMEMODE_LAPS:
447: $totalChecks = $this->currentMap->nbCheckpoints * $this->gameInfos->lapsNbLaps;
448: break;
449: case GameInfos::GAMEMODE_TEAM:
450: case GameInfos::GAMEMODE_ROUNDS:
451: case GameInfos::GAMEMODE_CUP:
452: if($this->currentMap->nbLaps > 0)
453: $totalChecks = $this->currentMap->nbCheckpoints * ($this->gameInfos->roundsForcedLaps ? : $this->currentMap->nbLaps);
454: else $totalChecks = $this->currentMap->nbCheckpoints;
455: break;
456: default:
457: $totalChecks = $this->currentMap->nbCheckpoints;
458: break;
459: }
460:
461: if(count($player->bestCheckpoints) != $totalChecks)
462: {
463: Console::println('Best time\'s checkpoint count does not match and was ignored!');
464: Console::printPlayerBest($player);
465: $player->bestTime = $oldBest;
466: return;
467: }
468:
469: Dispatcher::dispatch(new Event(Event::ON_PLAYER_NEW_BEST_TIME, $player, $oldBest, $timeOrScore));
470: }
471: }
472: break;
473: }
474: }
475:
476: function onPlayerIncoherence($playerUid, $login)
477: {
478:
479: }
480:
481: function onBillUpdated($billId, $state, $stateName, $transactionId)
482: {
483:
484: }
485:
486: function onTunnelDataReceived($playerUid, $login, $data)
487: {
488:
489: }
490:
491: function onMapListModified($curMapIndex, $nextMapIndex, $isListModified)
492: {
493: if($isListModified)
494: {
495: $maps = $this->connection->getMapList(-1, 0);
496:
497: foreach($maps as $key => $map)
498: {
499: $storageKey = array_search($map, $this->maps);
500: if($storageKey !== false) $maps[$key] = $this->maps[$storageKey];
501: else $this->maps[$storageKey] = null;
502: }
503: $this->maps = $maps;
504: }
505: $this->nextMap = isset($this->maps[$nextMapIndex]) ? $this->maps[$nextMapIndex] : null;
506: }
507:
508: function onPlayerInfoChanged($playerInfo)
509: {
510: $playerInfo = Player::fromArray($playerInfo);
511: $player = $this->getPlayerObject($playerInfo->login);
512: if(!$player) return;
513:
514: $wasSpectator = $player->spectator;
515: foreach($playerInfo as $key => $value)
516: $player->$key = $value;
517:
518: if($wasSpectator && !$player->spectator)
519: {
520: unset($this->spectators[$player->login]);
521: $this->players[$player->login] = $player;
522: Dispatcher::dispatch(new Event(Event::ON_PLAYER_CHANGE_SIDE, $player, 'spectator'));
523: }
524: else if(!$wasSpectator && $player->spectator)
525: {
526: unset($this->players[$player->login]);
527: $this->spectators[$player->login] = $player;
528: Dispatcher::dispatch(new Event(Event::ON_PLAYER_CHANGE_SIDE, $player, 'player'));
529: }
530: }
531:
532: function onManualFlowControlTransition($transition)
533: {
534:
535: }
536:
537: function onVoteUpdated($stateName, $login, $cmdName, $cmdParam)
538: {
539: if(!($this->currentVote instanceof Vote)) $this->currentVote = new Vote();
540: $this->currentVote->status = $stateName;
541: $this->currentVote->callerLogin = $login;
542: $this->currentVote->cmdName = $cmdName;
543: $this->currentVote->cmdParam = $cmdParam;
544: }
545:
546: function onModeScriptCallback($param1, $param2)
547: {
548:
549: }
550:
551:
552:
553: 554: 555: 556: 557:
558: function getPlayerObject($login)
559: {
560: if(isset($this->players[$login])) return $this->players[$login];
561: else if(isset($this->spectators[$login])) return $this->spectators[$login];
562: else return null;
563: }
564:
565: protected function updateRanking($rankings)
566: {
567: foreach($rankings as $ranking)
568: {
569: if($ranking->rank == 0) continue;
570:
571: $player = $this->getPlayerObject($ranking->login);
572: if(!$player) continue;
573:
574: $rankOld = $player->rank;
575: $player->rank = $ranking->rank;
576: $player->bestTime = $ranking->bestTime;
577: $player->bestCheckpoints = $ranking->bestCheckpoints;
578: $player->score = $ranking->score;
579: $player->nbrLapsFinished = $ranking->nbrLapsFinished;
580: $player->ladderScore = $ranking->ladderScore;
581:
582: if(!$player->isSpectator && $rankOld != $player->rank)
583: Dispatcher::dispatch(new Event(Event::ON_PLAYER_NEW_RANK, $player, $rankOld, $player->rank));
584: }
585: }
586:
587: protected function resetScores()
588: {
589: foreach($this->players as $player)
590: {
591: $player->bestTime = 0;
592: $player->rank = 0;
593: $player->point = 0;
594: }
595:
596: foreach($this->spectators as $spectator)
597: {
598: $spectator->bestTime = 0;
599: $spectator->rank = 0;
600: $spectator->point = 0;
601: }
602: }
603:
604: }
605:
606: ?>
607: