]> git.sesse.net Git - pkanalytics/blob - events.cpp
Support filtering passes by thrower and receiver.
[pkanalytics] / events.cpp
1 #include <algorithm>
2 #include <string>
3 #include <map>
4 #include <vector>
5 #include <optional>
6 #include <sqlite3.h>
7 #include "events.h"
8
9 using namespace std;
10
11 string format_timestamp(uint64_t pos);
12
13 static string event_type_to_string(EventType type)
14 {
15         switch (type) {
16         case EventType::CATCH:
17                 return "catch";
18         case EventType::DEFENSE:
19                 return "defense";
20         case EventType::DEFENSIVE_SOFT_MINUS:
21                 return "defensive_soft_minus";
22         case EventType::DEFENSIVE_SOFT_PLUS:
23                 return "defensive_soft_plus";
24         case EventType::FORMATION_DEFENSE:
25                 return "formation_defense";
26         case EventType::FORMATION_OFFENSE:
27                 return "formation_offense";
28         case EventType::DROP:
29                 return "drop";
30         case EventType::GOAL:
31                 return "goal";
32         case EventType::SWAP_IN:
33                 return "in";
34         case EventType::INTERCEPTION:
35                 return "interception";
36         case EventType::OFFENSIVE_SOFT_MINUS:
37                 return "offensive_soft_minus";
38         case EventType::OFFENSIVE_SOFT_PLUS:
39                 return "offensive_soft_plus";
40         case EventType::SWAP_OUT:
41                 return "out";
42         case EventType::PULL:
43                 return "pull";
44         case EventType::PULL_LANDED:
45                 return "pull_landed";
46         case EventType::PULL_OOB:
47                 return "pull_oob";
48         case EventType::RESTART:
49                 return "restart";
50         case EventType::SET_DEFENSE:
51                 return "set_defense";
52         case EventType::SET_OFFENSE:
53                 return "set_offense";
54         case EventType::STALLOUT:
55                 return "stallout";
56         case EventType::STOPPAGE:
57                 return "stoppage";
58         case EventType::THEIR_GOAL:
59                 return "their_goal";
60         case EventType::THEIR_PULL:
61                 return "their_pull";
62         case EventType::THEIR_THROWAWAY:
63                 return "their_throwaway";
64         case EventType::THROWAWAY:
65                 return "throwaway";
66         case EventType::UNKNOWN:
67                 return "unknown";
68         case EventType::WAS_D:
69                 return "was_d";
70         }
71         abort();
72 }
73
74 static EventType string_to_event_type(const string &type)
75 {
76         if (type == "catch") {
77                 return EventType::CATCH;
78         } else if (type == "defense") {
79                 return EventType::DEFENSE;
80         } else if (type == "defensive_soft_minus") {
81                 return EventType::DEFENSIVE_SOFT_MINUS;
82         } else if (type == "defensive_soft_plus") {
83                 return EventType::DEFENSIVE_SOFT_PLUS;
84         } else if (type == "formation_defense") {
85                 return EventType::FORMATION_DEFENSE;
86         } else if (type == "formation_offense") {
87                 return EventType::FORMATION_OFFENSE;
88         } else if (type == "drop") {
89                 return EventType::DROP;
90         } else if (type == "goal") {
91                 return EventType::GOAL;
92         } else if (type == "in") {
93                 return EventType::SWAP_IN;
94         } else if (type == "interception") {
95                 return EventType::INTERCEPTION;
96         } else if (type == "offensive_soft_minus") {
97                 return EventType::OFFENSIVE_SOFT_MINUS;
98         } else if (type == "offensive_soft_plus") {
99                 return EventType::OFFENSIVE_SOFT_PLUS;
100         } else if (type == "out") {
101                 return EventType::SWAP_OUT;
102         } else if (type == "pull") {
103                 return EventType::PULL;
104         } else if (type == "pull_landed") {
105                 return EventType::PULL_LANDED;
106         } else if (type == "pull_oob") {
107                 return EventType::PULL_OOB;
108         } else if (type == "restart") {
109                 return EventType::RESTART;
110         } else if (type == "set_defense") {
111                 return EventType::SET_DEFENSE;
112         } else if (type == "set_offense") {
113                 return EventType::SET_OFFENSE;
114         } else if (type == "stallout") {
115                 return EventType::STALLOUT;
116         } else if (type == "stoppage") {
117                 return EventType::STOPPAGE;
118         } else if (type == "their_goal") {
119                 return EventType::THEIR_GOAL;
120         } else if (type == "their_pull") {
121                 return EventType::THEIR_PULL;
122         } else if (type == "their_throwaway") {
123                 return EventType::THEIR_THROWAWAY;
124         } else if (type == "throwaway") {
125                 return EventType::THROWAWAY;
126         } else if (type == "unknown") {
127                 return EventType::UNKNOWN;
128         } else if (type == "was_d") {
129                 return EventType::WAS_D;
130         } else {
131                 fprintf(stderr, "Unknown event type ā€œ%sā€\n", type.c_str());
132                 exit(1);
133         }
134 }
135
136 EventsModel::EventsModel(sqlite3 *db, int match_id) : db(db), match_id(match_id)
137 {
138         load_data();
139 }
140
141 QVariant EventsModel::headerData(int section, Qt::Orientation orientation, int role) const
142 {
143         if (role != Qt::DisplayRole) {
144                 return QVariant();
145         }
146         if (orientation == Qt::Horizontal) {
147                 if (section == 0) {
148                         return "Time";
149                 } else if (section == 1) {
150                         return "Who/what";
151                 } else {
152                         return "Type";
153                 }
154         } else {
155                 return "";
156         }
157 }
158
159 QVariant EventsModel::data(const QModelIndex &index, int role) const
160 {
161         if (role != Qt::DisplayRole) {
162                 return QVariant();
163         }
164         const Event &e = events[index.row()];
165         if (index.column() == 0) {
166                 return QString::fromUtf8(format_timestamp(e.t));
167         } else if (index.column() == 1) {
168                 optional<int> player_id = e.player_id;
169                 optional<int> formation_id = e.formation_id;
170                 if (player_id) {
171                         auto p_it = players.find(*player_id);
172                         const Player &p = p_it->second;
173                         return QString::fromUtf8(p.name + " (" + p.number + ")");
174                 } else if (formation_id) {
175                         auto f_it = formations.find(*formation_id);
176                         const Formation &f = f_it->second;
177                         return QString::fromUtf8(f.name);
178                 } else if (e.type == EventType::FORMATION_OFFENSE || e.type == EventType::FORMATION_DEFENSE) {
179                         return "(None/unknown)";
180                 } else {
181                         return QVariant();
182                 }
183         } else if (index.column() == 2) {
184                 string type = event_type_to_string(e.type);
185                 type[0] = toupper(type[0]);
186                 for (char &ch : type) {
187                         if (ch == '_') {
188                                 ch = ' ';
189                         }
190                 }
191
192                 // Various fixups.
193                 if (type == "Pull oob") {
194                         type = "Pull OOB";
195                 } else if (type == "Formation defense") {
196                         type = "Defensive formation";
197                 } else if (type == "Formation offense") {
198                         type = "Offensive formation";
199                 } else if (type == "Set offense") {
200                         type = "On offense";
201                 } else if (type == "Set defense") {
202                         type = "On defense";
203                 } else if (type == "Catch") {
204                         type = "Catch/take";
205                 } else if (type == "Was d") {
206                         type = "Was d-ed";
207                 }
208
209                 return QString::fromUtf8(type);
210         }
211         return QVariant();
212 }
213
214 void EventsModel::load_data()
215 {
216         players.clear();
217         events.clear();
218
219         // Read the players. (The ordering is used to build the order map.)
220         sqlite3_stmt *stmt;
221         int ret = sqlite3_prepare_v2(db, "SELECT player, number, name FROM player ORDER BY gender, (number+0), number", -1, &stmt, 0);
222         if (ret != SQLITE_OK) {
223                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
224                 abort();
225         }
226         int order = 0;
227         for ( ;; ) {
228                 ret = sqlite3_step(stmt);
229                 if (ret == SQLITE_ROW) {
230                         Player p;
231                         p.player_id = sqlite3_column_int(stmt, 0);
232                         p.number = (const char *)sqlite3_column_text(stmt, 1);
233                         p.name = (const char *) sqlite3_column_text(stmt, 2);
234                         players[p.player_id] = std::move(p);
235                         player_ordering[p.player_id] = order++;
236                 } else if (ret == SQLITE_DONE) {
237                         break;
238                 } else {
239                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
240                         abort();
241                 }
242         }
243         ret = sqlite3_finalize(stmt);
244         if (ret != SQLITE_OK) {
245                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
246                 abort();
247         }
248
249         // Read the formations.
250         ret = sqlite3_prepare_v2(db, "SELECT formation, name FROM formation", -1, &stmt, 0);
251         if (ret != SQLITE_OK) {
252                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
253                 abort();
254         }
255         for ( ;; ) {
256                 ret = sqlite3_step(stmt);
257                 if (ret == SQLITE_ROW) {
258                         Formation f;
259                         f.formation_id = sqlite3_column_int(stmt, 0);
260                         f.name = (const char *) sqlite3_column_text(stmt, 1);
261                         formations[f.formation_id] = std::move(f);
262                 } else if (ret == SQLITE_DONE) {
263                         break;
264                 } else {
265                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
266                         abort();
267                 }
268         }
269         ret = sqlite3_finalize(stmt);
270         if (ret != SQLITE_OK) {
271                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
272                 abort();
273         }
274
275         // Read the events.
276         ret = sqlite3_prepare_v2(db, "SELECT event, t, player, formation, type FROM event WHERE match=? ORDER BY t", -1, &stmt, 0);
277         if (ret != SQLITE_OK) {
278                 fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
279                 abort();
280         }
281         sqlite3_bind_int64(stmt, 1, match_id);
282         for ( ;; ) {
283                 ret = sqlite3_step(stmt);
284                 if (ret == SQLITE_ROW) {
285                         Event e;
286                         e.event_id = sqlite3_column_int(stmt, 0);
287                         e.t = sqlite3_column_int(stmt, 1);
288                         if (sqlite3_column_type(stmt, 2) == SQLITE_INTEGER) {  // Non-NULL.
289                                 e.player_id = sqlite3_column_int(stmt, 2);
290                         }
291                         if (sqlite3_column_type(stmt, 3) == SQLITE_INTEGER) {  // Non-NULL.
292                                 e.formation_id = sqlite3_column_int(stmt, 3);
293                         }
294                         e.type = string_to_event_type((const char *)sqlite3_column_text(stmt, 4));
295                         events.push_back(std::move(e));
296                 } else if (ret == SQLITE_DONE) {
297                         break;
298                 } else {
299                         fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
300                         abort();
301                 }
302         }
303         ret = sqlite3_finalize(stmt);
304         if (ret != SQLITE_OK) {
305                 fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
306                 abort();
307         }
308 }
309
310 unsigned EventsModel::insert_event(uint64_t t, optional<int> player_id, optional<int> formation_id, const string &type)
311 {
312         auto it = lower_bound(events.begin(), events.end(), t,
313                 [](const Event &e, uint64_t t) { return e.t < t; });
314         unsigned pos = distance(events.begin(), it);
315         beginInsertRows(QModelIndex(), pos, pos);
316
317         Event e;
318         e.t = t;
319         e.player_id = player_id;
320         e.formation_id = formation_id;
321         e.type = string_to_event_type(type);
322         events.insert(events.begin() + pos, e);
323
324         endInsertRows();
325
326         // Insert the new row into the database.
327         sqlite3_stmt *stmt;
328         int ret = sqlite3_prepare_v2(db, "INSERT INTO event (match, t, player, formation, type) VALUES (?, ?, ?, ?, ?)", -1, &stmt, 0);
329         if (ret != SQLITE_OK) {
330                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
331                 abort();
332         }
333
334         sqlite3_bind_int64(stmt, 1, match_id);
335         sqlite3_bind_int64(stmt, 2, t);
336         if (player_id) {
337                 sqlite3_bind_int64(stmt, 3, *player_id);
338         } else {
339                 sqlite3_bind_null(stmt, 3);
340         }
341         if (formation_id) {
342                 sqlite3_bind_int64(stmt, 4, *formation_id);
343         } else {
344                 sqlite3_bind_null(stmt, 4);
345         }
346         sqlite3_bind_text(stmt, 5, type.data(), type.size(), SQLITE_STATIC);
347
348         ret = sqlite3_step(stmt);
349         if (ret == SQLITE_ROW) {
350                 fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
351                 abort();
352         }
353
354         ret = sqlite3_finalize(stmt);
355         if (ret != SQLITE_OK) {
356                 fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
357                 abort();
358         }
359
360         events[pos].event_id = sqlite3_last_insert_rowid(db);
361         return pos;
362 }
363
364 void EventsModel::delete_event(unsigned pos)
365 {
366         int event_id = events[pos].event_id;
367
368         beginRemoveRows(QModelIndex(), pos, pos);
369         events.erase(events.begin() + pos);
370         endRemoveRows();
371
372         // Delete the row from the database.
373         sqlite3_stmt *stmt;
374         int ret = sqlite3_prepare_v2(db, "DELETE FROM event WHERE event=?", -1, &stmt, 0);
375         if (ret != SQLITE_OK) {
376                 fprintf(stderr, "DELETE prepare: %s\n", sqlite3_errmsg(db));
377                 abort();
378         }
379
380         sqlite3_bind_int64(stmt, 1, event_id);
381
382         ret = sqlite3_step(stmt);
383         if (ret == SQLITE_ROW) {
384                 fprintf(stderr, "DELETE step: %s\n", sqlite3_errmsg(db));
385                 abort();
386         }
387
388         ret = sqlite3_finalize(stmt);
389         if (ret != SQLITE_OK) {
390                 fprintf(stderr, "DELETE finalize: %s\n", sqlite3_errmsg(db));
391                 abort();
392         }
393 }
394
395 void EventsModel::set_event_type(unsigned pos, const string &type)
396 {
397         events[pos].type = string_to_event_type(type);
398         emit dataChanged(createIndex(pos, 0), createIndex(pos, 2));
399
400         sqlite3_stmt *stmt;
401         int ret = sqlite3_prepare_v2(db, "UPDATE event SET type=? WHERE event=?", -1, &stmt, 0);
402         if (ret != SQLITE_OK) {
403                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
404                 abort();
405         }
406
407         sqlite3_bind_text(stmt, 1, type.data(), type.size(), SQLITE_STATIC);
408         sqlite3_bind_int64(stmt, 2, events[pos].event_id);
409
410         ret = sqlite3_step(stmt);
411         if (ret == SQLITE_ROW) {
412                 fprintf(stderr, "UPDATE step: %s\n", sqlite3_errmsg(db));
413                 abort();
414         }
415
416         ret = sqlite3_finalize(stmt);
417         if (ret != SQLITE_OK) {
418                 fprintf(stderr, "UPDATE finalize: %s\n", sqlite3_errmsg(db));
419                 abort();
420         }
421 }
422
423 void EventsModel::set_event_formation(unsigned pos, int formation_id)
424 {
425         events[pos].formation_id = formation_id;
426         emit dataChanged(createIndex(pos, 0), createIndex(pos, 2));
427
428         sqlite3_stmt *stmt;
429         int ret = sqlite3_prepare_v2(db, "UPDATE event SET formation=? WHERE event=?", -1, &stmt, 0);
430         if (ret != SQLITE_OK) {
431                 fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
432                 abort();
433         }
434
435         sqlite3_bind_int64(stmt, 1, formation_id);
436         sqlite3_bind_int64(stmt, 2, events[pos].event_id);
437
438         ret = sqlite3_step(stmt);
439         if (ret == SQLITE_ROW) {
440                 fprintf(stderr, "UPDATE step: %s\n", sqlite3_errmsg(db));
441                 abort();
442         }
443
444         ret = sqlite3_finalize(stmt);
445         if (ret != SQLITE_OK) {
446                 fprintf(stderr, "UPDATE finalize: %s\n", sqlite3_errmsg(db));
447                 abort();
448         }
449 }
450
451 unsigned EventsModel::get_last_event_pos(uint64_t t) const
452 {
453         // upper_bound() gives first where e.t > t,
454         // and the one before that is the one we want.
455         auto it = upper_bound(events.begin(), events.end(), t,
456                 [](uint64_t t, const Event &e) { return t < e.t; });
457         if (it == events.begin()) {
458                 return 0;
459         } else {
460                 return distance(events.begin(), it - 1);
461         }
462 }
463
464 EventsModel::Status EventsModel::get_status_at(uint64_t t)
465 {
466         Status s;
467         s.our_score = 0;
468         s.their_score = 0;
469         s.attack_state = Status::NOT_STARTED;
470         s.offensive_formation = 0;
471         s.defensive_formation = 0;
472         s.stoppage = false;
473         s.pull_state = Status::SHOULD_PULL;
474         s.last_catching_player = -1;
475         uint64_t last_gained_possession = 0;
476         uint64_t last_stoppage = 0;
477         uint64_t time_spent_in_stoppage = 0;
478         unsigned num_touches = 0;
479
480         auto set_offense = [&s] { s.attack_state = Status::OFFENSE; s.last_catching_player = -1; };
481         auto set_defense = [&s] { s.attack_state = Status::DEFENSE; s.last_catching_player = -1; };
482
483         for (const Event &e : events) {
484                 if (e.t > t) {
485                         break;
486                 }
487
488                 if (e.type == EventType::GOAL || e.type == EventType::THEIR_GOAL) {
489                         s.pull_state = Status::SHOULD_PULL;
490                 } else if (e.type == EventType::SWAP_IN || e.type == EventType::SWAP_OUT || e.type == EventType::STOPPAGE || e.type == EventType::RESTART || e.type == EventType::UNKNOWN || e.type == EventType::SET_DEFENSE || e.type == EventType::SET_OFFENSE) {
491                         // No effect on pull status.
492                 } else if (e.type == EventType::PULL) {
493                         s.pull_state = Status::PULL_IN_AIR;
494                         s.last_catching_player = -1;  // Just to be sure.
495                 } else {
496                         s.pull_state = Status::NOT_PULLING;  // Includes pull_landed and pull_oob.
497                 }
498
499                 if (e.type == EventType::SET_OFFENSE) {
500                         set_offense();
501                 } else if (e.type == EventType::SET_DEFENSE) {
502                         set_defense();
503                 }
504
505                 if (e.type == EventType::GOAL) {
506                         ++s.our_score;
507                         set_defense();
508                         num_touches = 0;
509                 }
510                 if (e.type == EventType::THEIR_GOAL) {
511                         ++s.their_score;
512                         set_offense();
513                         num_touches = 0;
514                 }
515                 if (e.type == EventType::CATCH) {
516                         if (num_touches == 0) {  // Pick up.
517                                 last_gained_possession = e.t;
518                                 time_spent_in_stoppage = 0;
519                         }
520                         ++num_touches;
521                         s.last_catching_player = *e.player_id;
522                 }
523                 if (e.type == EventType::INTERCEPTION) {
524                         num_touches = 1;
525                         set_offense();
526                         s.last_catching_player = *e.player_id;
527                         last_gained_possession = e.t;
528                         time_spent_in_stoppage = 0;
529                 }
530                 if (e.type == EventType::DEFENSE || e.type == EventType::THEIR_THROWAWAY) {
531                         set_offense();
532                         num_touches = 0;
533                         time_spent_in_stoppage = 0;
534                 }
535                 if (e.type == EventType::DROP || e.type == EventType::WAS_D || e.type == EventType::THROWAWAY || e.type == EventType::STALLOUT) {
536                         set_defense();
537                         num_touches = 0;
538                 }
539                 if (e.type == EventType::STOPPAGE) {
540                         s.stoppage = true;
541                         last_stoppage = e.t;
542                 }
543                 if (e.type == EventType::RESTART) {
544                         s.stoppage = false;
545                         if (last_stoppage != 0) {
546                                 time_spent_in_stoppage += (e.t - last_stoppage);
547                                 last_stoppage = 0;
548                         }
549                 }
550                 if (e.type == EventType::FORMATION_OFFENSE) {
551                         if (e.formation_id) {
552                                 s.offensive_formation = *e.formation_id;
553                         } else {
554                                 s.offensive_formation = 0;
555                         }
556                 }
557                 if (e.type == EventType::FORMATION_DEFENSE) {
558                         if (e.formation_id) {
559                                 s.defensive_formation = *e.formation_id;
560                         } else {
561                                 s.defensive_formation = 0;
562                         }
563                 }
564         }
565         if (s.stoppage && last_stoppage != 0) {
566                 time_spent_in_stoppage += (t - last_stoppage);
567         }
568
569         s.num_passes = (num_touches == 0) ? 0 : num_touches - 1;
570         s.possession_sec = (s.attack_state == Status::OFFENSE && last_gained_possession != 0 && num_touches != 0) ? (t - last_gained_possession - time_spent_in_stoppage) / 1000 : 0;
571         s.stoppage_sec = (s.attack_state == Status::OFFENSE && last_gained_possession != 0 && num_touches != 0) ? time_spent_in_stoppage / 1000 : 0;
572         return s;
573 }
574
575 set<int> EventsModel::get_team_at(uint64_t t)
576 {
577         set<int> team;
578         for (const Event &e : events) {
579                 if (e.t > t) {
580                         break;
581                 }
582                 if (e.type == EventType::SWAP_IN) {
583                         team.insert(*e.player_id);
584                 }
585                 if (e.type == EventType::SWAP_OUT) {
586                         team.erase(*e.player_id);
587                 }
588         }
589         return team;
590 }
591
592 void EventsModel::set_team_at(uint64_t t, const set<int> &new_team)
593 {
594         // Backdate to the last goal or stoppage, _or_ the last time someone
595         // going out is mentioned. (We don't really track injuries yet;
596         // do we want an explicit injury type? If we had one, it would probably
597         // be the simplest.)
598         uint64_t backdate_point = 0;
599         for (const Event &e : events) {
600                 if (e.t > t) {
601                         break;
602                 }
603                 if (e.type == EventType::GOAL || e.type == EventType::THEIR_GOAL || e.type == EventType::STOPPAGE || e.type == EventType::SET_OFFENSE || e.type == EventType::SET_DEFENSE) {
604                         backdate_point = e.t + 1;
605                 }
606                 if (e.player_id.has_value() && !new_team.count(*e.player_id)) {
607                         backdate_point = e.t + 1;
608                 }
609         }
610
611         // Delete all in/outs already at the backdate point.
612         for (unsigned i = 0; i < events.size(); ) {
613                 if (events[i].t > backdate_point) {
614                         break;
615                 }
616                 if (events[i].t == backdate_point && (events[i].type == EventType::SWAP_IN || events[i].type == EventType::SWAP_OUT)) {
617                         delete_event(i);
618                 } else {
619                         ++i;
620                 }
621         }
622
623         // Finally make the subs we need.
624         set<int> old_team = get_team_at(backdate_point);
625         for (int player_id : old_team) {
626                 if (!new_team.count(player_id)) {
627                         insert_event(backdate_point, player_id, nullopt, "out");
628                 }
629         }
630         for (int player_id : new_team) {
631                 if (!old_team.count(player_id)) {
632                         insert_event(backdate_point, player_id, nullopt, "in");
633                 }
634         }
635 }
636
637 void EventsModel::set_formation_at(uint64_t t, bool offense, unsigned formation)
638 {
639         // If there's another goal/stoppage/turnover no more than 20 seconds ago,
640         // we assume that the formation started at that point (it just took
641         // the operator a bit of time to see it). If not, we assume we
642         // changed in the middle of a point.
643         uint64_t backdate_point = 0;
644         for (const Event &e : events) {
645                 if (e.t > t) {
646                         break;
647                 }
648                 if (e.type == EventType::GOAL || e.type == EventType::THEIR_GOAL ||
649                     e.type == EventType::SWAP_IN || e.type == EventType::SWAP_OUT ||
650                     e.type == EventType::STOPPAGE ||
651                     e.type == EventType::SET_DEFENSE || e.type == EventType::SET_OFFENSE ||
652                     e.type == EventType::THROWAWAY || e.type == EventType::THEIR_THROWAWAY ||
653                     e.type == EventType::DROP || e.type == EventType::WAS_D || e.type == EventType::DEFENSE || e.type == EventType::INTERCEPTION || e.type == EventType::STALLOUT ||
654                     e.type == EventType::PULL || e.type == EventType::PULL_LANDED || e.type == EventType::PULL_OOB || e.type == EventType::THEIR_PULL ||
655                     e.type == EventType::FORMATION_OFFENSE || e.type == EventType::FORMATION_DEFENSE) {
656                         backdate_point = e.t + 1;
657                 }
658                 if (e.type == EventType::FORMATION_OFFENSE || e.type == EventType::FORMATION_DEFENSE) {
659                         backdate_point = 0;
660                 }
661         }
662         if (backdate_point != 0 && t - backdate_point < 20000) {
663                 t = backdate_point;
664         }
665         if (offense) {
666                 insert_event(t, nullopt, formation == 0 ? nullopt : optional{formation}, "formation_offense");
667         } else {
668                 insert_event(t, nullopt, formation == 0 ? nullopt : optional{formation}, "formation_defense");
669         }
670 }
671
672 vector<int> EventsModel::sort_team(const set<int> &team) const
673 {
674         vector<int> ret(team.begin(), team.end());
675         std::sort(ret.begin(), ret.end(), [this](int a, int b) {
676                 return player_ordering.find(a)->second < player_ordering.find(b)->second;
677         });
678         return ret;
679 }