1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file contains functionality relating to the official matches played in the league |
4
|
|
|
* |
5
|
|
|
* @package BZiON\Models |
6
|
|
|
* @license https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3 |
7
|
|
|
*/ |
8
|
|
|
use BZIon\Model\Column\Timestamp; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* A match played between two teams |
12
|
|
|
* @package BZiON\Models |
13
|
|
|
*/ |
14
|
|
|
class Match extends UrlModel implements NamedModel |
15
|
|
|
{ |
16
|
|
|
const OFFICIAL = "official"; |
17
|
|
|
const SPECIAL = "special"; |
18
|
|
|
const FUN = "fm"; |
19
|
|
|
|
20
|
|
|
const TEAM_V_TEAM = 0; |
21
|
|
|
const TEAM_V_MIXED = 1; |
22
|
|
|
const MIXED_V_MIXED = 2; |
23
|
|
|
|
24
|
|
|
use Timestamp; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* The ID of the first team of the match |
28
|
|
|
* @var int |
29
|
|
|
*/ |
30
|
|
|
protected $team_a; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* The ID of the second team of the match |
34
|
|
|
* @var int |
35
|
|
|
*/ |
36
|
|
|
protected $team_b; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* The color of the first team |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
protected $team_a_color; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The color of the second team |
46
|
|
|
* @var string |
47
|
|
|
*/ |
48
|
|
|
protected $team_b_color; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* The match points (usually the number of flag captures) Team A scored |
52
|
|
|
* @var int |
53
|
|
|
*/ |
54
|
|
|
protected $team_a_points; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* The match points Team B scored |
58
|
|
|
* @var int |
59
|
|
|
*/ |
60
|
|
|
protected $team_b_points; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* The callsigns players used during this match. |
64
|
|
|
* @var string[] |
65
|
|
|
*/ |
66
|
|
|
protected $player_callsigns; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* An associative array of IP addresses used by players during this match. |
70
|
|
|
* @var string[] |
71
|
|
|
*/ |
72
|
|
|
protected $player_ip_addresses; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* The players who participated in Team A during this match. |
76
|
|
|
* @var Player[] |
77
|
|
|
*/ |
78
|
|
|
protected $team_a_players; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* The players who participated in Team B during this match. |
82
|
|
|
* @var Player[] |
83
|
|
|
*/ |
84
|
|
|
protected $team_b_players; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* The ELO score of Team A after the match |
88
|
|
|
* @var int |
89
|
|
|
*/ |
90
|
|
|
protected $team_a_elo_new; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* The ELO score of Team B after the match |
94
|
|
|
* @var int |
95
|
|
|
*/ |
96
|
|
|
protected $team_b_elo_new; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* The map ID used in the match if the league supports more than one map |
100
|
|
|
* @var int |
101
|
|
|
*/ |
102
|
|
|
protected $map; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* The type of match that occurred. Valid options: official, fm, special |
106
|
|
|
* |
107
|
|
|
* @var string |
108
|
|
|
*/ |
109
|
|
|
protected $match_type; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* A JSON string of events that happened during a match, such as captures and substitutions |
113
|
|
|
* @var string |
114
|
|
|
*/ |
115
|
|
|
protected $match_details; |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* The ID of the server where this match took place |
119
|
|
|
* @var int |
120
|
|
|
*/ |
121
|
|
|
protected $server; |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* The server location of there the match took place |
125
|
|
|
* @var string |
126
|
|
|
*/ |
127
|
|
|
protected $server_address; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* The file name of the replay file of the match |
131
|
|
|
* @var string |
132
|
|
|
*/ |
133
|
|
|
protected $replay_file; |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* The value of the ELO score difference |
137
|
|
|
* @var int |
138
|
|
|
*/ |
139
|
|
|
protected $elo_diff; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* The value of the player Elo difference |
143
|
|
|
* @var int |
144
|
|
|
*/ |
145
|
|
|
protected $player_elo_diff; |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @var array |
149
|
|
|
*/ |
150
|
|
|
protected $player_elo_changelog; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* The timestamp representing when the match information was last updated |
154
|
|
|
* @var TimeDate |
155
|
|
|
*/ |
156
|
|
|
protected $updated; |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* The duration of the match in minutes |
160
|
|
|
* @var int |
161
|
|
|
*/ |
162
|
|
|
protected $duration; |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* The ID of the person (i.e. referee) who last updated the match information |
166
|
|
|
* @var string |
167
|
|
|
*/ |
168
|
|
|
protected $entered_by; |
169
|
|
|
|
170
|
|
|
const DEFAULT_STATUS = 'entered'; |
171
|
42 |
|
|
172
|
|
|
/** |
173
|
42 |
|
* The name of the database table used for queries |
174
|
42 |
|
*/ |
175
|
42 |
|
const TABLE = "matches"; |
176
|
42 |
|
|
177
|
42 |
|
const CREATE_PERMISSION = Permission::ENTER_MATCH; |
178
|
42 |
|
const EDIT_PERMISSION = Permission::EDIT_MATCH; |
179
|
42 |
|
const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH; |
180
|
42 |
|
const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH; |
181
|
42 |
|
|
182
|
42 |
|
/** |
183
|
42 |
|
* {@inheritdoc} |
184
|
42 |
|
*/ |
185
|
42 |
|
protected function assignResult($match) |
186
|
42 |
|
{ |
187
|
42 |
|
$this->team_a = $match['team_a']; |
188
|
42 |
|
$this->team_b = $match['team_b']; |
189
|
42 |
|
$this->team_a_color = $match['team_a_color']; |
190
|
42 |
|
$this->team_b_color = $match['team_b_color']; |
191
|
42 |
|
$this->team_a_points = $match['team_a_points']; |
192
|
42 |
|
$this->team_b_points = $match['team_b_points']; |
193
|
42 |
|
$this->team_a_elo_new = $match['team_a_elo_new']; |
194
|
42 |
|
$this->team_b_elo_new = $match['team_b_elo_new']; |
195
|
42 |
|
$this->map = $match['map']; |
196
|
|
|
$this->match_type = $match['match_type']; |
197
|
|
|
$this->match_details = $match['match_details']; |
198
|
|
|
$this->server_address = $match['server']; |
199
|
|
|
$this->replay_file = $match['replay_file']; |
200
|
|
|
$this->elo_diff = $match['elo_diff']; |
201
|
|
|
$this->player_elo_diff = $match['player_elo_diff']; |
202
|
1 |
|
$this->timestamp = TimeDate::fromMysql($match['timestamp']); |
203
|
|
|
$this->updated = TimeDate::fromMysql($match['updated']); |
204
|
1 |
|
$this->duration = $match['duration']; |
205
|
|
|
$this->entered_by = $match['entered_by']; |
206
|
|
|
$this->status = $match['status']; |
207
|
|
|
|
208
|
|
|
// For legacy support (e.g. Phinx migrations) we need to check if the key exists. Prior to migrations, it didn't |
209
|
|
|
// exist so Phinx migrations prior to 20170912201127_match_server_relationship will throw warnings. |
210
|
|
|
// |
211
|
|
|
// @todo Look for a better solution |
212
|
|
|
$this->server = isset($match['server_id']) ? $match['server_id'] : null; |
213
|
|
|
} |
214
|
1 |
|
|
215
|
|
|
/** |
216
|
1 |
|
* Get the name of the route that shows the object |
217
|
1 |
|
* @param string $action The route's suffix |
218
|
1 |
|
* @return string |
219
|
1 |
|
*/ |
220
|
|
|
public static function getRouteName($action = 'show') |
221
|
|
|
{ |
222
|
1 |
|
return "match_$action"; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Get a one word description of a match relative to a team (i.e. win, loss, or draw) |
227
|
|
|
* |
228
|
|
|
* @param int|string|TeamInterface $teamID The team ID we want the noun for |
229
|
|
|
* |
230
|
|
|
* @return string Either "win", "loss", or "draw" relative to the team |
231
|
|
|
*/ |
232
|
1 |
|
public function getMatchDescription($teamID) |
233
|
|
|
{ |
234
|
1 |
|
if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) { |
235
|
|
|
return "win"; |
236
|
|
|
} elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) { |
237
|
|
|
return "loss"; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
return "tie"; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
23 |
|
* Get a one letter description of a match relative to a team (i.e. W, L, or T) |
245
|
|
|
* |
246
|
23 |
|
* @param int|string|TeamInterface $teamID The team ID we want the noun for |
247
|
|
|
* |
248
|
23 |
|
* @return string Either "W", "L", or "T" relative to the team |
249
|
2 |
|
*/ |
250
|
|
|
public function getMatchLetter($teamID) |
251
|
|
|
{ |
252
|
|
|
return strtoupper(substr($this->getMatchDescription($teamID), 0, 1)); |
253
|
|
|
} |
254
|
23 |
|
|
255
|
23 |
|
/** |
256
|
|
|
* Get the score of a specific team |
257
|
|
|
* |
258
|
23 |
|
* @param int|string|TeamInterface $teamID The team we want the score for |
259
|
|
|
* |
260
|
|
|
* @return int The score that team received |
261
|
|
|
*/ |
262
|
|
View Code Duplication |
public function getScore($teamID) |
|
|
|
|
263
|
|
|
{ |
264
|
|
|
if ($teamID instanceof TeamInterface) { |
265
|
|
|
// Oh no! The caller gave us a Team model instead of an ID! |
266
|
|
|
$teamID = $teamID->getId(); |
267
|
|
|
} elseif (is_string($teamID)) { |
268
|
2 |
|
// Make sure we're comparing lowercase strings |
269
|
|
|
$teamID = strtolower($teamID); |
270
|
2 |
|
} |
271
|
|
|
|
272
|
|
|
if ($this->getTeamA()->getId() == $teamID) { |
273
|
|
|
return $this->getTeamAPoints(); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $this->getTeamBPoints(); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
36 |
|
* Get the score of the opponent relative to a team |
281
|
|
|
* |
282
|
36 |
|
* @param int|string|TeamInterface $teamID The opponent of the team we want the score for |
283
|
36 |
|
* |
284
|
2 |
|
* @return int The score of the opponent |
285
|
|
|
*/ |
286
|
|
|
public function getOpponentScore($teamID) |
287
|
|
|
{ |
288
|
36 |
|
return $this->getScore($this->getOpponent($teamID)); |
289
|
24 |
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
14 |
|
* Get the opponent of a match relative to a team ID |
293
|
|
|
* |
294
|
|
|
* @param int|string|TeamInterface $teamID The team who is known in a match |
295
|
|
|
* |
296
|
|
|
* @return TeamInterface The opponent team |
297
|
|
|
*/ |
298
|
|
View Code Duplication |
public function getOpponent($teamID) |
|
|
|
|
299
|
|
|
{ |
300
|
1 |
|
if ($teamID instanceof TeamInterface) { |
301
|
|
|
$teamID = $teamID->getId(); |
302
|
1 |
|
} elseif (is_string($teamID)) { |
303
|
|
|
$teamID = strtolower($teamID); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
if ($this->getTeamA()->getId() == $teamID) { |
307
|
|
|
return $this->getTeamB(); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return $this->getTeamA(); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Get the timestamp of the last update of the match |
315
|
|
|
* |
316
|
|
|
* @return TimeDate The match's update timestamp |
317
|
|
|
*/ |
318
|
|
|
public function getUpdated() |
319
|
|
|
{ |
320
|
|
|
return $this->updated->copy(); |
321
|
|
|
} |
322
|
41 |
|
|
323
|
|
|
/** |
324
|
41 |
|
* Set the timestamp of the match |
325
|
|
|
* |
326
|
41 |
|
* @param mixed $timestamp The match's new timestamp |
327
|
29 |
|
* @return $this |
328
|
|
|
*/ |
329
|
|
|
public function setTimestamp($timestamp) |
330
|
13 |
|
{ |
331
|
|
|
$this->updateProperty($this->timestamp, "timestamp", TimeDate::from($timestamp)); |
332
|
|
|
|
333
|
|
|
return $this; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
41 |
|
* Get the first team involved in the match |
338
|
|
|
* @return TeamInterface Team A |
339
|
41 |
|
*/ |
340
|
|
|
public function getTeamA() |
341
|
41 |
|
{ |
342
|
27 |
|
$team = Team::get($this->team_a); |
343
|
|
|
|
344
|
|
|
if ($this->match_type === self::OFFICIAL && $team->isValid()) { |
345
|
15 |
|
return $team; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
return new ColorTeam($this->team_a_color); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Get the second team involved in the match |
353
|
|
|
* @return TeamInterface Team B |
354
|
|
|
*/ |
355
|
|
|
public function getTeamB() |
356
|
|
|
{ |
357
|
|
|
$team = Team::get($this->team_b); |
358
|
|
|
|
359
|
|
|
if ($this->match_type === self::OFFICIAL && $team->isValid()) { |
360
|
|
|
return $team; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
return new ColorTeam($this->team_b_color); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Get the color of Team A |
368
|
|
|
* @return string |
369
|
|
|
*/ |
370
|
39 |
|
public function getTeamAColor() |
371
|
|
|
{ |
372
|
39 |
|
return $this->team_a_color; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Get the color of Team B |
377
|
|
|
* @return string |
378
|
|
|
*/ |
379
|
39 |
|
public function getTeamBColor() |
380
|
|
|
{ |
381
|
39 |
|
return $this->team_b_color; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Get the IP address a player used during this match. |
386
|
|
|
* |
387
|
|
|
* @param Player|int $id |
388
|
|
|
* |
389
|
4 |
|
* @return string|null |
390
|
|
|
*/ |
391
|
4 |
|
public function getPlayerIpAddress($id) |
392
|
1 |
|
{ |
393
|
|
|
$this->lazyLoadMatchParticipants(); |
394
|
|
|
|
395
|
4 |
|
if ($id instanceof Player) { |
396
|
1 |
|
$id = $id->getId(); |
397
|
4 |
|
} |
398
|
1 |
|
|
399
|
|
|
return __::get($this->player_ip_addresses, $id, null); |
400
|
|
|
} |
401
|
3 |
|
|
402
|
|
|
/** |
403
|
|
|
* Get the callsign a player used during this match. |
404
|
|
|
* |
405
|
|
|
* @param Player|int $id |
406
|
|
|
* |
407
|
|
|
* @return string|null |
408
|
|
|
*/ |
409
|
|
|
public function getPlayerCallsign($id) |
410
|
|
|
{ |
411
|
|
|
$this->lazyLoadMatchParticipants(); |
412
|
|
|
|
413
|
|
|
if ($id instanceof Player) { |
414
|
|
|
$id = $id->getId(); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return __::get($this->player_callsigns, $id, null); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Get the list of players on Team A who participated in this match |
422
|
|
|
* @return Player[] Returns null if there were no players recorded for this match |
423
|
|
|
*/ |
424
|
39 |
|
public function getTeamAPlayers() |
425
|
|
|
{ |
426
|
39 |
|
$this->lazyLoadMatchParticipants(); |
427
|
12 |
|
|
428
|
|
|
return $this->team_a_players; |
429
|
|
|
} |
430
|
30 |
|
|
431
|
|
|
/** |
432
|
|
|
* Get the list of players on Team B who participated in this match |
433
|
|
|
* @return Player[] Returns null if there were no players recorded for this match |
434
|
|
|
*/ |
435
|
|
|
public function getTeamBPlayers() |
436
|
|
|
{ |
437
|
28 |
|
$this->lazyLoadMatchParticipants(); |
438
|
|
|
|
439
|
28 |
|
return $this->team_b_players; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Get the list of players for a team in a match |
444
|
|
|
* @param Team|int|null The team or team ID |
445
|
|
|
* @return Player[]|null Returns null if there were no players recorded for this match |
446
|
28 |
|
*/ |
447
|
|
|
public function getPlayers($team = null) |
448
|
28 |
|
{ |
449
|
|
|
if ($team instanceof TeamInterface) { |
450
|
|
|
$team = $team->getId(); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
if ($this->getTeamA()->isValid() && $team === $this->getTeamA()->getId()) { |
454
|
|
|
return $this->getTeamAPlayers(); |
455
|
|
|
} elseif ($this->getTeamB()->isValid() && $team === $this->getTeamB()->getId()) { |
456
|
|
|
return $this->getTeamBPlayers(); |
457
|
|
|
} |
458
|
1 |
|
|
459
|
|
|
return array_merge($this->getTeamAPlayers(), $this->getTeamBPlayers()); |
460
|
1 |
|
} |
461
|
1 |
|
|
462
|
|
|
/** |
463
|
1 |
|
* Set the players of the match's teams. |
464
|
|
|
* |
465
|
|
|
* @param int[] $a_players An array of player IDs on Team A |
466
|
|
|
* @param int[] $b_players An array of player IDs on Team B |
467
|
|
|
* @param string[] $a_ips The IPs used by players on Team A. The order MUST match the order of $teamAPlayers |
468
|
|
|
* @param string[] $b_ips The IPs used by players on Team B. The order MUST match the order of $teamBPlayers |
469
|
|
|
* @param string[] $a_callsigns The callsigns used by players on Team A. The order MUST match the order of $teamAPlayers |
470
|
|
|
* @param string[] $b_callsigns The callsigns used by players on Team B. The order MUST match the order of $teamBPlayers |
471
|
|
|
* |
472
|
|
|
* @return self |
473
|
|
|
*/ |
474
|
|
|
public function setTeamPlayers($a_players = [], $b_players = [], $a_ips = [], $b_ips = [], $a_callsigns = [], $b_callsigns = []) |
475
|
|
|
{ |
476
|
|
|
$this->db->execute('DELETE FROM match_participation WHERE match_id = ?', [ |
477
|
|
|
$this->getId(), |
478
|
|
|
]); |
479
|
|
|
|
480
|
|
|
$matchParticipation = []; |
481
|
|
|
|
482
|
|
|
$this->matchParticipationEntryBuilder( |
483
|
|
|
$matchParticipation, |
484
|
|
|
($this->getTeamA() instanceof Team) ? $this->team_a : null, |
485
|
|
|
0, |
486
|
|
|
$a_players, |
487
|
|
|
$a_ips, |
488
|
|
|
$a_callsigns |
489
|
|
|
); |
490
|
|
|
$this->matchParticipationEntryBuilder( |
491
|
|
|
$matchParticipation, |
492
|
|
|
($this->getTeamB() instanceof Team) ? $this->team_b : null, |
493
|
|
|
1, |
494
|
|
|
$b_players, |
495
|
|
|
$b_ips, |
496
|
|
|
$b_callsigns |
497
|
30 |
|
); |
498
|
|
|
|
499
|
30 |
|
$this->db->insertBatch('match_participation', $matchParticipation); |
500
|
|
|
|
501
|
|
|
return $this; |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
/** |
505
|
|
|
* Load player participation in this match from its respective tables. |
506
|
|
|
*/ |
507
|
|
|
private function lazyLoadMatchParticipants() |
508
|
|
|
{ |
509
|
39 |
|
if ($this->team_a_players !== null || $this->team_b_players !== null) { |
510
|
|
|
return; |
511
|
39 |
|
} |
512
|
|
|
|
513
|
|
|
$participation = $this->db->query('SELECT * FROM match_participation WHERE match_id = ?', [ |
514
|
|
|
$this->getId(), |
515
|
|
|
]); |
516
|
|
|
|
517
|
1 |
|
$loyalty = __::groupBy($participation, 'team_loyalty'); |
518
|
|
|
|
519
|
1 |
|
$this->team_a_players = Player::arrayIdToModel(array_column(__::get($loyalty, 0, []), 'user_id')); |
|
|
|
|
520
|
1 |
|
$this->team_b_players = Player::arrayIdToModel(array_column(__::get($loyalty, 1, []), 'user_id')); |
|
|
|
|
521
|
|
|
|
522
|
|
|
$this->player_callsigns = array_column($participation, 'callsign', 'user_id'); |
523
|
1 |
|
$this->player_ip_addresses = array_column($participation, 'ip_address', 'user_id'); |
524
|
|
|
} |
525
|
1 |
|
|
526
|
1 |
|
/** |
527
|
1 |
|
* Get the first team's points |
528
|
1 |
|
* @return int Team A's points |
529
|
|
|
*/ |
530
|
|
|
public function getTeamAPoints() |
531
|
1 |
|
{ |
532
|
|
|
return $this->team_a_points; |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
/** |
536
|
|
|
* Get the second team's points |
537
|
|
|
* @return int Team B's points |
538
|
|
|
*/ |
539
|
|
|
public function getTeamBPoints() |
540
|
1 |
|
{ |
541
|
|
|
return $this->team_b_points; |
542
|
1 |
|
} |
543
|
|
|
|
544
|
1 |
|
/** |
545
|
1 |
|
* Set the match team points |
546
|
|
|
* |
547
|
|
|
* @param int $teamAPoints Team A's points |
548
|
1 |
|
* @param int $teamBPoints Team B's points |
549
|
|
|
* @return self |
550
|
|
|
*/ |
551
|
|
|
public function setTeamPoints($teamAPoints, $teamBPoints) |
552
|
|
|
{ |
553
|
|
|
$this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints); |
554
|
|
|
$this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints); |
555
|
|
|
|
556
|
|
|
return $this; |
557
|
|
|
} |
558
|
1 |
|
|
559
|
|
|
/** |
560
|
1 |
|
* Set the match team colors |
561
|
|
|
* |
562
|
1 |
|
* @param ColorTeam|string $teamAColor The color of team A |
563
|
1 |
|
* @param ColorTeam|string $teamBColor The color of team B |
564
|
|
|
* @return self |
565
|
|
|
*/ |
566
|
1 |
|
public function setTeamColors($teamAColor, $teamBColor) |
567
|
|
|
{ |
568
|
|
|
if ($this->isOfficial()) { |
569
|
|
|
throw new \Exception("Cannot change team colors in an official match"); |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
if ($teamAColor instanceof ColorTeam) { |
573
|
|
|
$teamAColor = $teamAColor->getId(); |
574
|
|
|
} |
575
|
|
|
if ($teamBColor instanceof ColorTeam) { |
576
|
|
|
$teamBColor = $teamBColor->getId(); |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
$this->updateProperty($this->team_a_color, "team_a_color", $teamAColor); |
580
|
|
|
$this->updateProperty($this->team_b_color, "team_b_color", $teamBColor); |
581
|
|
|
} |
582
|
|
|
|
583
|
7 |
|
/** |
584
|
|
|
* Get the ELO difference applied to each team's old ELO |
585
|
7 |
|
* |
586
|
|
|
* @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference |
587
|
|
|
* |
588
|
|
|
* @return int The ELO difference |
589
|
|
|
*/ |
590
|
|
|
public function getEloDiff($absoluteValue = true) |
591
|
|
|
{ |
592
|
7 |
|
return ($absoluteValue) ? abs($this->elo_diff) : $this->elo_diff; |
593
|
|
|
} |
594
|
7 |
|
|
595
|
|
|
/** |
596
|
|
|
* Get the Elo difference applied to players |
597
|
|
|
* |
598
|
|
|
* @param bool $absoluteValue Whether or not to get the absolute value of the Elo difference |
599
|
|
|
* |
600
|
|
|
* @return int The Elo difference for players |
601
|
7 |
|
*/ |
602
|
|
|
public function getPlayerEloDiff($absoluteValue = true) |
603
|
7 |
|
{ |
604
|
|
|
return ($absoluteValue && $this->player_elo_diff !== null) ? abs($this->player_elo_diff) : $this->player_elo_diff; |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
/** |
608
|
|
|
* Get the changelog for the player Elos for this match and cache them |
609
|
|
|
*/ |
610
|
7 |
|
private function getPlayerEloChangelog() |
611
|
|
|
{ |
612
|
7 |
|
if ($this->player_elo_changelog !== null) { |
613
|
|
|
return; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
$results = $this->db->query('SELECT * FROM player_elo WHERE match_id = ?', $this->getId()); |
617
|
|
|
|
618
|
|
|
foreach ($results as $result) { |
619
|
|
|
$this->player_elo_changelog[$result['user_id']] = [ |
620
|
|
|
'before' => $result['elo_previous'], |
621
|
1 |
|
'after' => $result['elo_new'] |
622
|
|
|
]; |
623
|
1 |
|
} |
624
|
1 |
|
} |
625
|
1 |
|
|
626
|
1 |
|
/** |
627
|
|
|
* Get the Elo for the player before this match occurred |
628
|
|
|
* |
629
|
|
|
* @param Player $player |
630
|
|
|
* |
631
|
|
|
* @return null|int |
632
|
|
|
*/ |
633
|
|
View Code Duplication |
public function getPlayerEloBefore(Player $player) |
|
|
|
|
634
|
|
|
{ |
635
|
|
|
$this->getPlayerEloChangelog(); |
636
|
|
|
|
637
|
|
|
if (isset($this->player_elo_changelog[$player->getId()])) { |
638
|
1 |
|
return $this->player_elo_changelog[$player->getId()]['before']; |
639
|
|
|
} |
640
|
1 |
|
|
641
|
1 |
|
return null; |
642
|
1 |
|
} |
643
|
1 |
|
|
644
|
|
|
/** |
645
|
|
|
* Get the Elo for the player after this match occurred |
646
|
|
|
* |
647
|
|
|
* @param Player $player |
648
|
|
|
* |
649
|
|
|
* @return null|int |
650
|
|
|
*/ |
651
|
|
View Code Duplication |
public function getPlayerEloAfter(Player $player) |
|
|
|
|
652
|
|
|
{ |
653
|
1 |
|
$this->getPlayerEloChangelog(); |
654
|
|
|
|
655
|
1 |
|
if (isset($this->player_elo_changelog[$player->getId()])) { |
656
|
|
|
return $this->player_elo_changelog[$player->getId()]['after']; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return null; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Set the Elo difference applied to players |
664
|
|
|
* |
665
|
|
|
* @param int $diff |
666
|
|
|
*/ |
667
|
|
|
public function setPlayerEloDiff($diff) |
668
|
|
|
{ |
669
|
|
|
$this->updateProperty($this->player_elo_diff, 'player_elo_diff', $diff); |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
/** |
673
|
|
|
* Get the first team's new ELO |
674
|
|
|
* @return int Team A's new ELO |
675
|
|
|
*/ |
676
|
|
|
public function getTeamAEloNew() |
677
|
|
|
{ |
678
|
|
|
return $this->team_a_elo_new; |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
/** |
682
|
23 |
|
* Get the second team's new ELO |
683
|
|
|
* @return int Team B's new ELO |
684
|
23 |
|
*/ |
685
|
9 |
|
public function getTeamBEloNew() |
686
|
14 |
|
{ |
687
|
9 |
|
return $this->team_b_elo_new; |
688
|
|
|
} |
689
|
|
|
|
690
|
5 |
|
/** |
691
|
|
|
* Get the first team's old ELO |
692
|
|
|
* @return int |
693
|
|
|
*/ |
694
|
|
|
public function getTeamAEloOld() |
695
|
|
|
{ |
696
|
|
|
return $this->team_a_elo_new - $this->elo_diff; |
697
|
|
|
} |
698
|
23 |
|
|
699
|
|
|
/** |
700
|
23 |
|
* Get the second team's old ELO |
701
|
|
|
* @return int |
702
|
|
|
*/ |
703
|
|
|
public function getTeamBEloOld() |
704
|
|
|
{ |
705
|
|
|
return $this->team_b_elo_new + $this->elo_diff; |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
/** |
709
|
|
|
* Get the team's new ELO |
710
|
|
|
* @param Team $team The team whose new ELO to return |
711
|
|
|
* @return int|null The new ELO, or null if the team provided has not |
712
|
|
|
* participated in the match |
713
|
|
|
*/ |
714
|
|
|
public function getTeamEloNew(Team $team) |
715
|
|
|
{ |
716
|
|
|
if ($team->getId() == $this->team_a) { |
717
|
|
|
return $this->getTeamAEloNew(); |
718
|
|
|
} elseif ($team->getId() == $this->team_b) { |
719
|
|
|
return $this->getTeamBEloNew(); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
return null; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
/** |
726
|
|
|
* Get the team's old ELO |
727
|
|
|
* @param Team $team The team whose old ELO to return |
728
|
1 |
|
* @return int|null The old ELO, or null if the team provided has not |
729
|
|
|
* participated in the match |
730
|
1 |
|
*/ |
731
|
|
|
public function getTeamEloOld(Team $team) |
732
|
|
|
{ |
733
|
|
|
if ($team->getId() == $this->team_a) { |
734
|
|
|
return $this->getTeamAEloOld(); |
735
|
|
|
} elseif ($team->getId() == $this->team_b) { |
736
|
|
|
return $this->getTeamBEloOld(); |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
return null; |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
/** |
743
|
|
|
* Get the map where the match was played on |
744
|
|
|
* @return Map Returns an invalid map if no map was found |
745
|
|
|
*/ |
746
|
|
|
public function getMap() |
747
|
|
|
{ |
748
|
|
|
return Map::get($this->map); |
749
|
|
|
} |
750
|
|
|
|
751
|
|
|
/** |
752
|
1 |
|
* Set the map where the match was played |
753
|
|
|
* @param int $map The ID of the map |
754
|
1 |
|
* @return self |
755
|
|
|
*/ |
756
|
|
|
public function setMap($map) |
757
|
|
|
{ |
758
|
1 |
|
$this->updateProperty($this->map, "map", $map, "s"); |
759
|
|
|
|
760
|
|
|
return $this; |
761
|
|
|
} |
762
|
|
|
|
763
|
|
|
/** |
764
|
|
|
* Get the type of official match this is. Whether it has just traditional teams or has mixed teams. |
765
|
6 |
|
* |
766
|
|
|
* Possible official match types: |
767
|
6 |
|
* - Team vs Team |
768
|
|
|
* - Team vs Mixed |
769
|
|
|
* - Mixed vs Mixed |
770
|
|
|
* |
771
|
|
|
* @see Match::TEAM_V_TEAM |
772
|
|
|
* @see Match::TEAM_V_MIXED |
773
|
|
|
* @see Match::MIXED_V_MIXED |
774
|
|
|
* |
775
|
|
|
* @return int |
776
|
|
|
*/ |
777
|
|
|
public function getTeamMatchType() |
778
|
|
|
{ |
779
|
|
|
if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) { |
780
|
|
|
return self::TEAM_V_TEAM; |
781
|
|
|
} elseif ($this->getTeamA()->supportsMatchCount() xor $this->getTeamB()->supportsMatchCount()) { |
782
|
|
|
return self::TEAM_V_MIXED; |
783
|
|
|
} |
784
|
|
|
|
785
|
2 |
|
return self::MIXED_V_MIXED; |
786
|
|
|
} |
787
|
2 |
|
|
788
|
|
|
/** |
789
|
|
|
* Get the match type |
790
|
|
|
* |
791
|
|
|
* @return string 'official', 'fm', or 'special' |
792
|
|
|
*/ |
793
|
|
|
public function getMatchType() |
794
|
|
|
{ |
795
|
36 |
|
return $this->match_type; |
796
|
|
|
} |
797
|
|
|
|
798
|
36 |
|
/** |
799
|
|
|
* Set the match type |
800
|
|
|
* |
801
|
36 |
|
* @param string $matchType A valid match type; official, fm, special |
802
|
|
|
* |
803
|
|
|
* @return static |
804
|
|
|
*/ |
805
|
|
|
public function setMatchType($matchType) |
806
|
|
|
{ |
807
|
|
|
return $this->updateProperty($this->match_type, "match_type", $matchType, 's'); |
808
|
|
|
} |
809
|
36 |
|
|
810
|
|
|
/** |
811
|
|
|
* Get a JSON decoded array of events that occurred during the match |
812
|
|
|
* @return mixed|null Returns null if there were no events recorded for the match |
813
|
|
|
*/ |
814
|
|
|
public function getMatchDetails() |
815
|
36 |
|
{ |
816
|
22 |
|
return json_decode($this->match_details); |
817
|
15 |
|
} |
818
|
12 |
|
|
819
|
3 |
|
/** |
820
|
|
|
* Get the server this match took place on |
821
|
2 |
|
* |
822
|
1 |
|
* @return Server |
823
|
|
|
*/ |
824
|
|
|
public function getServer() |
825
|
|
|
{ |
826
|
|
|
return Server::get($this->server); |
827
|
1 |
|
} |
828
|
|
|
|
829
|
|
|
/** |
830
|
|
|
* Set the server this match took place on |
831
|
|
|
* |
832
|
|
|
* @param int $serverID |
833
|
|
|
* |
834
|
39 |
|
* @return $this |
835
|
|
|
*/ |
836
|
39 |
|
public function setServer($serverID = null) |
837
|
|
|
{ |
838
|
|
|
$this->updateProperty($this->server, 'server_id', $serverID); |
839
|
|
|
|
840
|
|
|
return $this; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
/** |
844
|
|
|
* Get the server address of the server where this match took place |
845
|
1 |
|
* |
846
|
|
|
* @deprecated 0.10.0 Use Match::getServer() instead. Using this function is reserved for migrations/legacy support. |
847
|
1 |
|
* |
848
|
|
|
* @see 20170912201127_match_server_relationship.php |
849
|
|
|
* |
850
|
|
|
* @return string|null Returns null if there was no server address recorded |
851
|
|
|
*/ |
852
|
|
|
public function getServerAddress() |
853
|
22 |
|
{ |
854
|
|
|
return $this->server_address; |
855
|
22 |
|
} |
856
|
|
|
|
857
|
|
|
/** |
858
|
|
|
* Get the name of the replay file for this specific map |
859
|
|
|
* @param int $length The length of the replay file name; it will be truncated |
860
|
|
|
* @return string Returns null if there was no replay file name recorded |
861
|
|
|
*/ |
862
|
|
|
public function getReplayFileName($length = 0) |
863
|
|
|
{ |
864
|
|
|
if ($length > 0) { |
865
|
|
|
return substr($this->replay_file, 0, $length); |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
return $this->replay_file; |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
/** |
872
|
|
|
* Get the match duration |
873
|
|
|
* @return int The duration of the match in minutes |
874
|
|
|
*/ |
875
|
|
|
public function getDuration() |
876
|
|
|
{ |
877
|
|
|
return $this->duration; |
878
|
1 |
|
} |
879
|
|
|
|
880
|
1 |
|
/** |
881
|
1 |
|
* Set the match duration |
882
|
|
|
* |
883
|
1 |
|
* @param int $duration The new duration of the match in minutes |
884
|
|
|
* @return self |
885
|
1 |
|
*/ |
886
|
|
|
public function setDuration($duration) |
887
|
|
|
{ |
888
|
|
|
return $this->updateProperty($this->duration, "duration", $duration); |
889
|
1 |
|
} |
890
|
|
|
|
891
|
|
|
/** |
892
|
|
|
* Get the user who entered the match |
893
|
|
|
* @return Player |
894
|
|
|
*/ |
895
|
|
|
public function getEnteredBy() |
896
|
|
|
{ |
897
|
|
|
return Player::get($this->entered_by); |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
/** |
901
|
|
|
* Get the loser of the match |
902
|
|
|
* |
903
|
|
|
* @return TeamInterface The team that was the loser or the team with the lower elo if the match was a draw |
904
|
|
|
*/ |
905
|
|
|
public function getLoser() |
906
|
|
|
{ |
907
|
42 |
|
// Get the winner of the match |
908
|
|
|
$winner = $this->getWinner(); |
909
|
|
|
|
910
|
42 |
|
// Get the team that wasn't the winner... Duh |
911
|
|
|
return $this->getOpponent($winner); |
912
|
42 |
|
} |
913
|
23 |
|
|
914
|
20 |
|
/** |
915
|
14 |
|
* Get the winner of a match |
916
|
|
|
* |
917
|
|
|
* @return TeamInterface The team that was the victor or the team with the lower elo if the match was a draw |
918
|
42 |
|
*/ |
919
|
42 |
|
public function getWinner() |
920
|
3 |
|
{ |
921
|
|
|
// "As Mother Teresa once said, it's not enough if you win. Others must lose." |
922
|
|
|
// -Stephen Colbert |
923
|
|
|
|
924
|
|
|
// Get the team that had its Elo increased or the team whose players had their Elo increased |
925
|
|
|
if ($this->elo_diff > 0 || $this->player_elo_diff > 0) { |
926
|
|
|
return $this->getTeamA(); |
927
|
|
|
} elseif ($this->elo_diff < 0 || $this->player_elo_diff < 0) { |
928
|
39 |
|
return $this->getTeamB(); |
929
|
|
|
} elseif ($this->team_a_points > $this->team_b_points) { |
930
|
39 |
|
// In case we're dealing with a match such an FM that doesn't have an ELO difference |
931
|
39 |
|
return $this->getTeamA(); |
932
|
|
|
} elseif ($this->team_a_points < $this->team_b_points) { |
933
|
|
|
return $this->getTeamB(); |
934
|
39 |
|
} |
935
|
28 |
|
|
936
|
28 |
|
// If the scores are the same, return Team A because well, fuck you that's why |
937
|
|
|
return $this->getTeamA(); |
938
|
28 |
|
} |
939
|
|
|
|
940
|
|
|
/** |
941
|
|
|
* Determine whether the match was a draw |
942
|
|
|
* @return bool True if the match ended without any winning teams |
943
|
|
|
*/ |
944
|
|
|
public function isDraw() |
945
|
|
|
{ |
946
|
39 |
|
return $this->team_a_points == $this->team_b_points; |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
/** |
950
|
|
|
* Find out whether the match involves a team |
951
|
39 |
|
* |
952
|
23 |
|
* @param TeamInterface $team The team to check |
953
|
17 |
|
* @return bool |
954
|
11 |
|
*/ |
955
|
11 |
|
public function involvesTeam($team) |
956
|
|
|
{ |
957
|
11 |
|
return $team->getId() == $this->getTeamA()->getId() || $team->getId() == $this->getTeamB()->getId(); |
958
|
|
|
} |
959
|
|
|
|
960
|
|
|
/** |
961
|
39 |
|
* Find out if the match is played between official teams |
962
|
39 |
|
*/ |
963
|
39 |
|
public function isOfficial() |
964
|
|
|
{ |
965
|
|
|
return self::OFFICIAL === $this->getMatchType(); |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
/** |
969
|
|
|
* Reset the ELOs of the teams participating in the match |
970
|
|
|
* |
971
|
|
|
* @return self |
972
|
|
|
*/ |
973
|
|
|
public function resetTeamElos() |
974
|
|
|
{ |
975
|
|
|
if ($this->match_type === self::OFFICIAL) { |
976
|
|
|
$this->getTeamA()->supportsMatchCount() && $this->getTeamA()->changeELO(-$this->elo_diff); |
|
|
|
|
977
|
|
|
$this->getTeamB()->supportsMatchCount() && $this->getTeamB()->changeELO(+$this->elo_diff); |
|
|
|
|
978
|
|
|
} |
979
|
|
|
|
980
|
|
|
return $this; |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
/** |
984
|
|
|
* Calculate the match's contribution to the team activity |
985
|
|
|
* |
986
|
|
|
* @return float |
987
|
|
|
*/ |
988
|
|
|
public function getActivity() |
989
|
|
|
{ |
990
|
45 |
|
$daysPassed = $this->getTimestamp()->diffInSeconds(); |
991
|
|
|
$daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY; |
992
|
|
|
|
993
|
|
|
$activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0); |
994
|
|
|
|
995
|
|
|
if (is_nan($activity) || $activity < 0.0) { |
996
|
45 |
|
return 0.0; |
997
|
45 |
|
} |
998
|
45 |
|
|
999
|
45 |
|
return $activity; |
1000
|
45 |
|
} |
1001
|
45 |
|
|
1002
|
45 |
|
/** |
1003
|
45 |
|
* Calculate the Elo differences for players and teams for a given match. |
1004
|
45 |
|
* |
1005
|
45 |
|
* @param Team $a |
1006
|
45 |
|
* @param Team $b |
1007
|
45 |
|
* @param int $a_points |
1008
|
45 |
|
* @param int $b_points |
1009
|
45 |
|
* @param int[]|Player[] $a_players |
1010
|
|
|
* @param int[]|Player[] $b_players |
1011
|
|
|
* @param int $duration |
1012
|
|
|
* |
1013
|
45 |
|
* @throws InvalidArgumentException When a "Mixed" team is entered without a player roster |
1014
|
|
|
* |
1015
|
45 |
|
* @return array |
1016
|
42 |
|
*/ |
1017
|
42 |
|
private static function calculateElos($a, $b, $a_points, $b_points, $a_players, $b_players, $duration) |
1018
|
|
|
{ |
1019
|
42 |
|
// Get the type of official match |
1020
|
|
|
$matchType = Match::MIXED_V_MIXED; |
|
|
|
|
1021
|
39 |
|
|
1022
|
39 |
|
if ($a->supportsMatchCount() && $b->supportsMatchCount()) { |
1023
|
|
|
$matchType = Match::TEAM_V_TEAM; |
|
|
|
|
1024
|
|
|
} elseif ($a->supportsMatchCount() xor $b->supportsMatchCount()) { |
1025
|
39 |
|
$matchType = Match::TEAM_V_MIXED; |
|
|
|
|
1026
|
29 |
|
} |
1027
|
|
|
|
1028
|
29 |
|
if ($matchType == Match::TEAM_V_MIXED && |
|
|
|
|
1029
|
29 |
|
((!$a->isValid() && empty($a_players)) || (!$b->isValid() && empty($b_players)))) { |
1030
|
|
|
throw new InvalidArgumentException('A Mixed team must have a player roster to calculate the Elo for team Elo differences'); |
1031
|
39 |
|
} |
1032
|
27 |
|
|
1033
|
|
|
// |
1034
|
27 |
|
// Handle Player Elo Diff Calculations |
1035
|
27 |
|
// |
1036
|
|
|
|
1037
|
|
|
// By default, we won't have a player Elo difference since we won't force matches to have a roster |
1038
|
|
|
$playerEloDiff = null; |
1039
|
42 |
|
|
1040
|
42 |
|
$a_players_elo = 1200; |
1041
|
42 |
|
$b_players_elo = 1200; |
1042
|
|
|
|
1043
|
42 |
|
// Only bother to calculate a player Elo diff if we have players reported for both teams |
1044
|
|
|
if (!empty($a_players) && !empty($b_players)) { |
1045
|
|
|
$a_players_elo = self::getAveragePlayerElo($a_players); |
1046
|
|
|
$b_players_elo = self::getAveragePlayerElo($b_players); |
1047
|
|
|
|
1048
|
|
|
$playerEloDiff = self::calculateEloDiff($a_players_elo, $b_players_elo, $a_points, $b_points, $duration); |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
// |
1052
|
|
|
// Handle Team Elo Diff Calculations |
1053
|
|
|
// |
1054
|
|
|
|
1055
|
|
|
// By default, we'll assume a Mixed vs Mixed official match where Elos do not change for teams |
1056
|
|
|
$teamEloDiff = null; |
1057
|
|
|
|
1058
|
|
|
// Work with calculations for team Elos to handle the following situations: |
1059
|
39 |
|
// - Team vs Team :: Use team Elos for calculations |
1060
|
|
|
// - Team vs Mixed :: Use team Elo and the player average Elo for the "Mixed" team |
1061
|
39 |
|
if ($matchType == Match::TEAM_V_TEAM) { |
|
|
|
|
1062
|
39 |
|
$teamEloDiff = self::calculateEloDiff($a->getElo(), $b->getElo(), $a_points, $b_points, $duration); |
1063
|
14 |
|
} elseif ($matchType == Match::TEAM_V_MIXED) { |
|
|
|
|
1064
|
27 |
|
$a_team_elo = ($a->supportsMatchCount()) ? $a->getElo() : $a_players_elo; |
1065
|
19 |
|
$b_team_elo = ($b->supportsMatchCount()) ? $b->getElo() : $b_players_elo; |
1066
|
|
|
|
1067
|
8 |
|
$teamEloDiff = self::calculateEloDiff($a_team_elo, $b_team_elo, $a_points, $b_points, $duration); |
1068
|
|
|
} |
1069
|
|
|
|
1070
|
|
|
return [ |
1071
|
39 |
|
'match_type' => $matchType, |
1072
|
39 |
|
'team_elo' => $teamEloDiff, |
1073
|
|
|
'player_elo' => $playerEloDiff |
1074
|
39 |
|
]; |
1075
|
|
|
} |
1076
|
2 |
|
|
1077
|
|
|
/** |
1078
|
|
|
* Enter a new match to the database |
1079
|
|
|
* @param int $a Team A's ID |
1080
|
37 |
|
* @param int $b Team B's ID |
1081
|
|
|
* @param int $a_points Team A's match points |
1082
|
|
|
* @param int $b_points Team B's match points |
1083
|
|
|
* @param int $duration The match duration in minutes |
1084
|
|
|
* @param int|null $entered_by The ID of the player reporting the match |
1085
|
|
|
* @param string|DateTime $timestamp When the match was played |
1086
|
|
|
* @param int[] $a_players The IDs of the first team's players |
1087
|
|
|
* @param int[] $b_players The IDs of the second team's players |
1088
|
|
|
* @param string|null $server The address of the server where the match was played |
1089
|
|
|
* @param string $replayFile The name of the replay file of the match |
1090
|
|
|
* @param int $map The ID of the map where the match was played, only for rotational leagues |
1091
|
|
|
* @param string $matchType The type of match (e.g. official, fm, special) |
1092
|
|
|
* @param string $a_color Team A's color |
1093
|
|
|
* @param string $b_color Team b's color |
1094
|
|
|
* @param string[] $a_ipAddresses The IP addresses of players on Team A. The order of this array should |
1095
|
|
|
* match the order of $a_players |
1096
|
|
|
* @param string[] $b_ipAddresses The IP addresses of players on Team B. The order of this array should |
1097
|
|
|
* match the order of $b_players |
1098
|
|
|
* @param string[] $a_callsigns The callsigns of players on Team A. The order of this array should match |
1099
|
|
|
* the order of $a_players |
1100
|
3 |
|
* @param string[] $b_callsigns The callsigns of players on Team B. The order of this array should match |
1101
|
|
|
* the order of $b_players |
1102
|
3 |
|
* |
1103
|
|
|
* @throws \Exception When no testing environment has been configured for the database. |
1104
|
|
|
* @throws InvalidArgumentException When a ColorTeam is selected for an official match and no players are defined |
1105
|
|
|
* for that team |
1106
|
3 |
|
* |
1107
|
3 |
|
* @return Match An object representing the match that was just entered |
1108
|
|
|
*/ |
1109
|
3 |
|
public static function enterMatch( |
1110
|
|
|
$a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now", |
1111
|
3 |
|
$a_players = array(), $b_players = array(), $server = null, $replayFile = null, |
1112
|
3 |
|
$map = null, $matchType = "official", $a_color = null, $b_color = null, |
1113
|
|
|
$a_ipAddresses = array(), $b_ipAddresses = array(), $a_callsigns = array(), $b_callsigns = array() |
1114
|
|
|
) { |
1115
|
3 |
|
$matchData = array( |
1116
|
3 |
|
'team_a_color' => strtolower($a_color), |
1117
|
3 |
|
'team_b_color' => strtolower($b_color), |
1118
|
3 |
|
'team_a_points' => $a_points, |
1119
|
3 |
|
'team_b_points' => $b_points, |
1120
|
|
|
'timestamp' => TimeDate::from($timestamp)->toMysql(), |
1121
|
|
|
'duration' => $duration, |
1122
|
3 |
|
'entered_by' => $entered_by, |
1123
|
|
|
'server' => $server, |
1124
|
3 |
|
'replay_file' => $replayFile, |
1125
|
3 |
|
'map' => $map, |
1126
|
|
|
'status' => 'entered', |
1127
|
3 |
|
'match_type' => $matchType |
1128
|
3 |
|
); |
1129
|
3 |
|
|
1130
|
|
|
// (P)layer Elo Diff and (T)eam Elo Diff; respectively |
1131
|
|
|
$tEloDiff = null; |
|
|
|
|
1132
|
3 |
|
|
1133
|
3 |
|
if ($matchType === self::OFFICIAL) { |
1134
|
3 |
|
$team_a = Team::get($a); |
1135
|
|
|
$team_b = Team::get($b); |
1136
|
|
|
|
1137
|
3 |
|
$eloCalcs = self::calculateElos($team_a, $team_b, $a_points, $b_points, $a_players, $b_players, $duration); |
1138
|
3 |
|
|
1139
|
|
|
$matchData['elo_diff'] = $tEloDiff = $eloCalcs['team_elo']; |
1140
|
|
|
$matchData['player_elo_diff'] = $eloCalcs['player_elo']; |
1141
|
|
|
|
1142
|
|
|
// Update team ELOs |
1143
|
1 |
|
if ($team_a->isValid()) { |
1144
|
|
|
$team_a->adjustElo($tEloDiff); |
1145
|
1 |
|
|
1146
|
|
|
$matchData['team_a'] = $a; |
1147
|
|
|
$matchData['team_a_elo_new'] = $team_a->getElo(); |
1148
|
|
|
} |
1149
|
|
|
if ($team_b->isValid()) { |
1150
|
|
|
$team_b->adjustElo(-$tEloDiff); |
1151
|
|
|
|
1152
|
26 |
|
$matchData['team_b'] = $b; |
1153
|
|
|
$matchData['team_b_elo_new'] = $team_b->getElo(); |
1154
|
26 |
|
} |
1155
|
26 |
|
} |
1156
|
|
|
|
1157
|
|
|
$match = self::create($matchData, 'updated'); |
1158
|
|
|
$match->setTeamPlayers( |
1159
|
|
|
$a_players, $b_players, |
1160
|
|
|
$a_ipAddresses, $b_ipAddresses, |
1161
|
|
|
$a_callsigns, $b_callsigns |
1162
|
|
|
); |
1163
|
|
|
$match->updateMatchCount(); |
1164
|
|
|
$match->updatePlayerElo(); |
1165
|
|
|
|
1166
|
|
|
return $match; |
1167
|
|
|
} |
1168
|
|
|
|
1169
|
|
|
/** |
1170
|
|
|
* Calculate the ELO score difference |
1171
|
1 |
|
* |
1172
|
|
|
* Computes the ELO score difference on each team after a match, based on |
1173
|
1 |
|
* GU League's rules. |
1174
|
|
|
* |
1175
|
1 |
|
* @param int $a_elo Team A's current ELO score |
1176
|
1 |
|
* @param int $b_elo Team B's current ELO score |
1177
|
|
|
* @param int $a_points Team A's match points |
1178
|
|
|
* @param int $b_points Team B's match points |
1179
|
|
|
* @param int $duration The match duration in minutes |
1180
|
|
|
* @return int The ELO score difference |
1181
|
3 |
|
*/ |
1182
|
|
|
public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration) |
1183
|
3 |
|
{ |
1184
|
|
|
$prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0))); |
1185
|
|
|
if ($a_points > $b_points) { |
1186
|
|
|
$diff = 50 * (1 - $prob); |
1187
|
|
|
} elseif ($a_points == $b_points) { |
1188
|
|
|
$diff = 50 * (0.5 - $prob); |
1189
|
22 |
|
} else { |
1190
|
|
|
$diff = 50 * (0 - $prob); |
1191
|
22 |
|
} |
1192
|
|
|
|
1193
|
22 |
|
// Apply ELO modifiers from `config.yml` |
1194
|
22 |
|
$durations = Service::getParameter('bzion.league.duration'); |
1195
|
|
|
$diff *= (isset($durations[$duration])) ? $durations[$duration] : 1; |
1196
|
21 |
|
|
1197
|
9 |
|
if (abs($diff) < 1 && $diff != 0) { |
1198
|
|
|
// ELOs such as 0.75 should round up to 1... |
1199
|
21 |
|
return ($diff > 0) ? 1 : -1; |
1200
|
|
|
} |
1201
|
2 |
|
|
1202
|
2 |
|
// ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48) |
1203
|
2 |
|
return intval($diff); |
1204
|
|
|
} |
1205
|
|
|
|
1206
|
|
|
/** |
1207
|
|
|
* Find if a match's stored ELO is correct |
1208
|
|
|
*/ |
1209
|
|
|
public function isEloCorrect() |
1210
|
|
|
{ |
1211
|
|
|
return $this->elo_diff === $this->calculateEloDiff( |
1212
|
|
|
$this->getTeamAEloOld(), |
1213
|
22 |
|
$this->getTeamBEloOld(), |
1214
|
22 |
|
$this->getTeamAPoints(), |
1215
|
22 |
|
$this->getTeamBPoints(), |
1216
|
22 |
|
$this->getDuration() |
1217
|
22 |
|
); |
1218
|
22 |
|
} |
1219
|
|
|
|
1220
|
|
|
/** |
1221
|
|
|
* Remove Elo recordings for players participating in this match |
1222
|
|
|
*/ |
1223
|
|
|
public function resetPlayerElos() |
1224
|
|
|
{ |
1225
|
|
|
$this->db->execute('DELETE FROM player_elo WHERE match_id = ?', [$this->getId()]); |
1226
|
|
|
} |
1227
|
|
|
|
1228
|
|
|
/** |
1229
|
|
|
* Recalculate the match's elo and adjust the team ELO values |
1230
|
|
|
*/ |
1231
|
|
|
public function recalculateElo() |
1232
|
|
|
{ |
1233
|
|
|
if ($this->match_type !== self::OFFICIAL) { |
1234
|
|
|
return; |
1235
|
|
|
} |
1236
|
|
|
|
1237
|
1 |
|
$a = $this->getTeamA(); |
1238
|
|
|
$b = $this->getTeamB(); |
1239
|
|
|
|
1240
|
|
|
$this->resetPlayerElos(); |
1241
|
|
|
|
1242
|
|
|
foreach ($this->getPlayers() as $player) { |
|
|
|
|
1243
|
1 |
|
$player->invalidateMatchFromCache($this); |
1244
|
1 |
|
} |
1245
|
1 |
|
|
1246
|
1 |
|
$eloCalcs = self::calculateElos( |
1247
|
1 |
|
$a, $b, |
|
|
|
|
1248
|
|
|
$this->getTeamAPoints(), $this->getTeamBPoints(), |
1249
|
|
|
$this->getTeamAPlayers(), $this->getTeamBPlayers(), |
1250
|
1 |
|
$this->getDuration() |
1251
|
|
|
); |
1252
|
|
|
|
1253
|
1 |
|
$elo = $eloCalcs['team_elo']; |
1254
|
|
|
|
1255
|
|
|
$this->updateProperty($this->elo_diff, 'elo_diff', $elo); |
1256
|
|
|
$this->updateProperty($this->player_elo_diff, 'player_elo_diff', $eloCalcs['player_elo']); |
1257
|
1 |
|
|
1258
|
|
|
if ($a->supportsMatchCount()) { |
1259
|
1 |
|
$a->adjustElo($elo); |
1260
|
|
|
$this->updateProperty($this->team_a_elo_new, 'team_a_elo_new', $a->getElo()); |
1261
|
|
|
} |
1262
|
|
|
|
1263
|
1 |
|
if ($b->supportsMatchCount()) { |
1264
|
1 |
|
$b->adjustElo(-$elo); |
1265
|
1 |
|
$this->updateProperty($this->team_b_elo_new, 'team_b_elo_new', $b->getElo()); |
1266
|
|
|
} |
1267
|
1 |
|
|
1268
|
1 |
|
$this->updatePlayerElo(); |
1269
|
1 |
|
} |
1270
|
|
|
|
1271
|
|
|
/** |
1272
|
1 |
|
* Get all the matches in the database |
1273
|
|
|
*/ |
1274
|
1 |
|
public static function getMatches() |
1275
|
|
|
{ |
1276
|
|
|
return self::getQueryBuilder()->active()->getModels(); |
1277
|
|
|
} |
1278
|
1 |
|
|
1279
|
|
|
/** |
1280
|
|
|
* Get a query builder for matches |
1281
|
|
|
* @return MatchQueryBuilder |
1282
|
|
|
*/ |
1283
|
1 |
|
public static function getQueryBuilder() |
1284
|
|
|
{ |
1285
|
|
|
return new MatchQueryBuilder('Match', array( |
1286
|
|
|
'columns' => array( |
1287
|
1 |
|
'firstTeam' => 'team_a', |
1288
|
|
|
'secondTeam' => 'team_b', |
1289
|
|
|
'firstTeamPoints' => 'team_a_points', |
1290
|
|
|
'secondTeamPoints' => 'team_b_points', |
1291
|
|
|
'time' => 'timestamp', |
1292
|
|
|
'map' => 'map', |
1293
|
|
|
'server' => 'server_id', |
1294
|
|
|
'type' => 'match_type', |
1295
|
1 |
|
'status' => 'status' |
1296
|
|
|
), |
1297
|
1 |
|
)); |
1298
|
1 |
|
} |
1299
|
|
|
|
1300
|
|
|
/** |
1301
|
|
|
* {@inheritdoc} |
1302
|
|
|
*/ |
1303
|
|
|
public function delete() |
1304
|
|
|
{ |
1305
|
|
|
$this->updateMatchCount(true); |
1306
|
|
|
|
1307
|
|
|
parent::delete(); |
1308
|
|
|
} |
1309
|
28 |
|
|
1310
|
28 |
|
/** |
1311
|
2 |
|
* {@inheritdoc} |
1312
|
|
|
*/ |
1313
|
|
|
public function getName() |
1314
|
28 |
|
{ |
1315
|
28 |
|
$description = ''; |
1316
|
|
|
|
1317
|
28 |
|
switch ($this->getMatchType()) { |
1318
|
|
|
case self::OFFICIAL: |
1319
|
|
|
// Only show Elo diff if both teams are actual teams |
1320
|
|
|
if ($this->getTeamA()->supportsMatchCount() && $this->getTeamB()->supportsMatchCount()) { |
1321
|
|
|
$description = "(+/- {$this->getEloDiff()})"; |
1322
|
|
|
} |
1323
|
|
|
break; |
1324
|
|
|
|
1325
|
42 |
|
case self::FUN: |
1326
|
|
|
$description = 'Fun Match:'; |
1327
|
42 |
|
break; |
1328
|
4 |
|
|
1329
|
|
|
case self::SPECIAL: |
1330
|
|
|
$description = 'Special Match:'; |
1331
|
39 |
|
break; |
1332
|
|
|
|
1333
|
39 |
|
default: |
1334
|
19 |
|
break; |
1335
|
19 |
|
} |
1336
|
|
|
|
1337
|
21 |
|
return trim(sprintf('%s %s [%d] vs [%d] %s', |
1338
|
21 |
|
$description, |
1339
|
|
|
$this->getWinner()->getName(), |
1340
|
39 |
|
$this->getScore($this->getWinner()), |
1341
|
|
|
$this->getScore($this->getLoser()), |
1342
|
|
|
$this->getLoser()->getName() |
1343
|
|
|
)); |
1344
|
|
|
} |
1345
|
42 |
|
|
1346
|
|
|
/** |
1347
|
42 |
|
* Recalculates match history for all teams and matches |
1348
|
4 |
|
* |
1349
|
|
|
* Recalculation is done as follows: |
1350
|
|
|
* 1. A match is chosen as a starting point - it's stored old team ELOs are |
1351
|
39 |
|
* considered correct |
1352
|
|
|
* 2. Team ELOs are reset to their values at the starting point |
1353
|
39 |
|
* 3. Each match that occurred since the first specified match has its ELO |
1354
|
28 |
|
* recalculated based on the current team values, and the new match data |
1355
|
28 |
|
* and team ELOs are stored in the database |
1356
|
|
|
* |
1357
|
|
|
* @param Match $match The first match |
1358
|
39 |
|
* |
1359
|
29 |
|
* @throws Exception |
1360
|
29 |
|
*/ |
1361
|
|
|
public static function recalculateMatchesSince(Match $match) |
1362
|
39 |
|
{ |
1363
|
|
|
try { |
1364
|
|
|
// Commented out to prevent ridiculously large recalculations |
1365
|
|
|
//set_time_limit(0); |
1366
|
|
|
|
1367
|
|
|
$query = Match::getQueryBuilder() |
|
|
|
|
1368
|
|
|
->where('status')->notEquals('deleted') |
1369
|
|
|
->where('type')->equals(Match::OFFICIAL) |
|
|
|
|
1370
|
|
|
->where('time')->isAfter($match->getTimestamp(), $inclusive = true) |
1371
|
|
|
->sortBy('time'); |
1372
|
|
|
|
1373
|
|
|
/** @var Match[] $matches */ |
1374
|
|
|
$matches = $query->getModels($fast = true); |
1375
|
|
|
|
1376
|
|
|
// Send the total count to client-side javascript |
1377
|
|
|
echo count($matches) . "\n"; |
1378
|
|
|
|
1379
|
|
|
// Start a transaction so tables are locked and we don't stay with |
1380
|
|
|
// messed up data if something goes wrong |
1381
|
|
|
Database::getInstance()->startTransaction(); |
1382
|
|
|
|
1383
|
|
|
$teamsReset = []; |
1384
|
|
|
|
1385
|
|
|
// Reset match teams, in case the selected match is deleted and does |
1386
|
|
|
// not show up in the list of matches to recalculate |
1387
|
|
|
if ($match->getTeamA()->supportsMatchCount()) { |
1388
|
|
|
$match->getTeamA()->setElo($match->getTeamAEloOld()); |
|
|
|
|
1389
|
|
|
$teamsReset[ $match->getTeamA()->getId() ] = true; |
1390
|
|
|
} |
1391
|
|
|
if ($match->getTeamB()->supportsMatchCount()) { |
1392
|
|
|
$match->getTeamB()->setElo($match->getTeamBEloOld()); |
|
|
|
|
1393
|
|
|
$teamsReset[ $match->getTeamB()->getId() ] = true; |
1394
|
|
|
} |
1395
|
|
|
|
1396
|
|
|
foreach ($matches as $i => &$match) { |
1397
|
|
|
// Reset teams' ELOs if they haven't been reset already |
1398
|
|
View Code Duplication |
if ($match->getTeamA()->supportsMatchCount() && !isset($teamsReset[ $match->getTeamA()->getId() ])) { |
|
|
|
|
1399
|
|
|
$teamsReset[ $match->getTeamA()->getId() ] = true; |
1400
|
|
|
$match->getTeamA()->setElo($match->getTeamAEloOld()); |
|
|
|
|
1401
|
|
|
} |
1402
|
|
View Code Duplication |
if ($match->getTeamB()->supportsMatchCount() && !isset($teamsReset[ $match->getTeamB()->getId() ])) { |
|
|
|
|
1403
|
|
|
$teamsReset[ $match->getTeamB()->getId() ] = true; |
1404
|
|
|
$match->getTeamB()->setElo($match->getTeamBEloOld()); |
|
|
|
|
1405
|
|
|
} |
1406
|
|
|
|
1407
|
|
|
$match->recalculateElo(); |
1408
|
|
|
|
1409
|
|
|
// Send an update to the client-side javascript, so that a |
1410
|
|
|
// progress bar can be updated |
1411
|
|
|
echo "m"; |
1412
|
|
|
} |
1413
|
|
|
} catch (Exception $e) { |
1414
|
|
|
Database::getInstance()->rollback(); |
1415
|
|
|
Database::getInstance()->finishTransaction(); |
1416
|
|
|
throw $e; |
1417
|
|
|
} |
1418
|
|
|
|
1419
|
|
|
Database::getInstance()->finishTransaction(); |
1420
|
|
|
|
1421
|
|
|
echo "\n\nCalculation successful\n"; |
1422
|
|
|
} |
1423
|
|
|
|
1424
|
|
|
/** |
1425
|
|
|
* Get the average ELO for an array of players |
1426
|
|
|
* |
1427
|
|
|
* @param int[]|Player[] $players |
1428
|
|
|
* |
1429
|
|
|
* @return float|int |
1430
|
|
|
*/ |
1431
|
|
|
private static function getAveragePlayerElo($players) |
1432
|
|
|
{ |
1433
|
|
|
$getElo = function ($n) { |
1434
|
|
|
if ($n instanceof Player) { |
1435
|
|
|
return $n->getElo(); |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
|
|
return Player::get($n)->getElo(); |
1439
|
|
|
}; |
1440
|
|
|
|
1441
|
|
|
return array_sum(array_map($getElo, $players)) / count($players); |
1442
|
|
|
} |
1443
|
|
|
|
1444
|
|
|
/** |
1445
|
|
|
* Update the match count of the teams participating in the match |
1446
|
|
|
* |
1447
|
|
|
* @param bool $decrement Whether to decrement instead of incrementing the match count |
1448
|
|
|
*/ |
1449
|
|
|
private function updateMatchCount($decrement = false) |
1450
|
|
|
{ |
1451
|
|
|
if ($this->match_type !== self::OFFICIAL) { |
1452
|
|
|
return; |
1453
|
|
|
} |
1454
|
|
|
|
1455
|
|
|
$diff = ($decrement) ? -1 : 1; |
1456
|
|
|
|
1457
|
|
|
if ($this->isDraw()) { |
1458
|
|
|
$this->getTeamA()->supportsMatchCount() && $this->getTeamA()->changeMatchCount($diff, 'draw'); |
|
|
|
|
1459
|
|
|
$this->getTeamB()->supportsMatchCount() && $this->getTeamB()->changeMatchCount($diff, 'draw'); |
|
|
|
|
1460
|
|
|
} else { |
1461
|
|
|
$this->getWinner()->supportsMatchCount() && $this->getWinner()->changeMatchCount($diff, 'win'); |
|
|
|
|
1462
|
|
|
$this->getLoser()->supportsMatchCount() && $this->getLoser()->changeMatchCount($diff, 'loss'); |
|
|
|
|
1463
|
|
|
} |
1464
|
|
|
} |
1465
|
|
|
|
1466
|
|
|
/** |
1467
|
|
|
* Update the Elos for the participating players in a match |
1468
|
|
|
*/ |
1469
|
|
|
private function updatePlayerElo() |
1470
|
|
|
{ |
1471
|
|
|
if ($this->match_type !== self::OFFICIAL || $this->getPlayerEloDiff() === null) { |
1472
|
|
|
return; |
1473
|
|
|
} |
1474
|
|
|
|
1475
|
|
|
$eloDiff = $this->getPlayerEloDiff(false); |
1476
|
|
|
|
1477
|
|
|
foreach ($this->getTeamAPlayers() as $player) { |
1478
|
|
|
$player->adjustElo($eloDiff, $this); |
1479
|
|
|
$player->setLastMatch($this->getId()); |
1480
|
|
|
} |
1481
|
|
|
|
1482
|
|
|
foreach ($this->getTeamBPlayers() as $player) { |
1483
|
|
|
$player->adjustElo(-$eloDiff, $this); |
1484
|
|
|
$player->setLastMatch($this->getId()); |
1485
|
|
|
} |
1486
|
|
|
} |
1487
|
|
|
|
1488
|
|
|
/** |
1489
|
|
|
* Build an array of match participation records for a given match. |
1490
|
|
|
* |
1491
|
|
|
* @param array $storage The referenced array that'll be storing all of the created references. |
1492
|
|
|
* @param int|null $teamID The ID of the Team this player played for, or NULL if players didn't play for a team |
1493
|
|
|
* @param int $teamLoyalty Representation for team color: 0 for "Team A" or 1 for "Team B" |
1494
|
|
|
* @param int[] $playerIDs The BZiON player IDs that played for this team |
1495
|
|
|
* @param string[] $ipAddresses The IP addresses for the recorded players, the order must match the order of $playerIDs |
1496
|
|
|
* @param string[] $callsigns The callsigns for the recorded players, the order must match the order of $playerIDs |
1497
|
|
|
*/ |
1498
|
|
|
private function matchParticipationEntryBuilder(array &$storage, $teamID, $teamLoyalty, array $playerIDs, array $ipAddresses = [], array $callsigns = []) |
1499
|
|
|
{ |
1500
|
|
|
foreach ($playerIDs as $index => $playerID) { |
1501
|
|
|
if (empty($playerID)) { |
1502
|
|
|
continue; |
1503
|
|
|
} |
1504
|
|
|
|
1505
|
|
|
$workspace = [ |
1506
|
|
|
'match_id' => $this->getId(), |
1507
|
|
|
'user_id' => $playerID, |
1508
|
|
|
'team_id' => $teamID, |
1509
|
|
|
'callsign' => __::get($callsigns, $index, null), |
1510
|
|
|
'ip_address' => __::get($ipAddresses, $index, null), |
1511
|
|
|
'team_loyalty' => $teamLoyalty, |
1512
|
|
|
]; |
1513
|
|
|
|
1514
|
|
|
$storage[] = $workspace; |
1515
|
|
|
} |
1516
|
|
|
} |
1517
|
|
|
} |
1518
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.