Finnaly readind replays

In plans: advanced stats, saving those stats into DB
in order not to use szy api shitton of times
Mb even new graphs idk idk
This commit is contained in:
dan63047 2024-01-06 02:11:45 +03:00
parent 9bf80f651e
commit 8dc2a5bced
9 changed files with 130 additions and 15 deletions

View File

@ -856,6 +856,12 @@ class EndContextMulti {
playstyleTracking = [for (int i = 0; i < secondaryTracking.length; i++) Playstyle(secondaryTracking[i], tertiaryTracking[i], nerdStatsTracking[i].app, nerdStatsTracking[i].vsapm, nerdStatsTracking[i].dsp, nerdStatsTracking[i].gbe, estTrTracking[i].srarea, estTrTracking[i].statrank)]; playstyleTracking = [for (int i = 0; i < secondaryTracking.length; i++) Playstyle(secondaryTracking[i], tertiaryTracking[i], nerdStatsTracking[i].app, nerdStatsTracking[i].vsapm, nerdStatsTracking[i].dsp, nerdStatsTracking[i].gbe, estTrTracking[i].srarea, estTrTracking[i].statrank)];
} }
@override
bool operator == (covariant EndContextMulti other){
if (userId != other.userId) return false;
return true;
}
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['user'] = {'_id': userId, 'username': username}; data['user'] = {'_id': userId, 'username': username};

View File

@ -49,6 +49,8 @@ class ReplayStats{
late Finesse finesse; late Finesse finesse;
late int kills; late int kills;
double get finessePercentage => finesse.perfectPieces / piecesPlaced;
ReplayStats({ ReplayStats({
required this.seed, required this.seed,
required this.linesCleared, required this.linesCleared,
@ -116,6 +118,7 @@ class ReplayStats{
class ReplayData{ class ReplayData{
late String id; late String id;
late Map<dynamic, dynamic> rawJson;
late List<EndContextMulti> endcontext; late List<EndContextMulti> endcontext;
late List<List<ReplayStats>> stats; late List<List<ReplayStats>> stats;
late List<ReplayStats> totalStats; late List<ReplayStats> totalStats;
@ -126,10 +129,15 @@ class ReplayData{
required this.id, required this.id,
required this.endcontext, required this.endcontext,
required this.stats, required this.stats,
required this.roundLengths required this.totalStats,
}); required this.roundLengths,
required this.totalLength
}){
rawJson = {};
}
ReplayData.fromJson(Map<String, dynamic> json){ ReplayData.fromJson(Map<String, dynamic> json){
rawJson = json;
id = json["_id"]; id = json["_id"];
endcontext = [EndContextMulti.fromJson(json["endcontext"][0]), EndContextMulti.fromJson(json["endcontext"][1])]; endcontext = [EndContextMulti.fromJson(json["endcontext"][0]), EndContextMulti.fromJson(json["endcontext"][1])];
roundLengths = []; roundLengths = [];

View File

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// Locales: 2
/// Strings: 988 (494 per locale) /// Strings: 994 (497 per locale)
/// ///
/// Built on 2024-01-01 at 16:00 UTC /// Built on 2024-01-05 at 16:51 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@ -178,6 +178,8 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get tlLeaderboard => 'Tetra League leaderboard'; String get tlLeaderboard => 'Tetra League leaderboard';
String get noRecords => 'No records'; String get noRecords => 'No records';
String get noRecord => 'No record'; String get noRecord => 'No record';
String get botRecord => 'Bots are not allowed to set records';
String get anonRecord => 'Guests are not allowed to set records';
String get notEnoughData => 'Not enough data'; String get notEnoughData => 'Not enough data';
String get noHistorySaved => 'No history saved'; String get noHistorySaved => 'No history saved';
String obtainDate({required Object date}) => 'Obtained ${date}'; String obtainDate({required Object date}) => 'Obtained ${date}';
@ -204,6 +206,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get exactValue => 'Exact value'; String get exactValue => 'Exact value';
String get neverPlayedTL => 'That user never played Tetra League'; String get neverPlayedTL => 'That user never played Tetra League';
String get botTL => 'Bots are not allowed to play Tetra League'; String get botTL => 'Bots are not allowed to play Tetra League';
String get anonTL => 'Guests are not allowed to play Tetra League';
String get exportDB => 'Export local database'; String get exportDB => 'Export local database';
String get exportDBDescription => 'It contains states and Tetra League records of the tracked players and list of tracked players.'; String get exportDBDescription => 'It contains states and Tetra League records of the tracked players and list of tracked players.';
String get desktopExportAlertTitle => 'Desktop export'; String get desktopExportAlertTitle => 'Desktop export';
@ -756,6 +759,8 @@ class _StringsRu implements Translations {
@override String get tlLeaderboard => 'Рейтинговая таблица'; @override String get tlLeaderboard => 'Рейтинговая таблица';
@override String get noRecords => 'Нет записей'; @override String get noRecords => 'Нет записей';
@override String get noRecord => 'Нет рекорда'; @override String get noRecord => 'Нет рекорда';
@override String get botRecord => 'Ботам нельзя ставить рекорды';
@override String get anonRecord => 'Гостям нельзя ставить рекорды';
@override String get notEnoughData => 'Недостаточно данных'; @override String get notEnoughData => 'Недостаточно данных';
@override String get noHistorySaved => 'Нет сохранённой истории'; @override String get noHistorySaved => 'Нет сохранённой истории';
@override String obtainDate({required Object date}) => 'Получено ${date}'; @override String obtainDate({required Object date}) => 'Получено ${date}';
@ -782,6 +787,7 @@ class _StringsRu implements Translations {
@override String get exactValue => 'Точное значение'; @override String get exactValue => 'Точное значение';
@override String get neverPlayedTL => 'Этот игрок никогда не играл в Тетра Лигу'; @override String get neverPlayedTL => 'Этот игрок никогда не играл в Тетра Лигу';
@override String get botTL => 'Ботам нельзя играть в Тетра Лигу'; @override String get botTL => 'Ботам нельзя играть в Тетра Лигу';
@override String get anonTL => 'Гостям нельзя играть в Тетра Лигу';
@override String get exportDB => 'Экспортировать локальную базу данных'; @override String get exportDB => 'Экспортировать локальную базу данных';
@override String get exportDBDescription => 'Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.'; @override String get exportDBDescription => 'Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.';
@override String get desktopExportAlertTitle => 'Экспорт на десктопе'; @override String get desktopExportAlertTitle => 'Экспорт на десктопе';
@ -1326,6 +1332,8 @@ extension on Translations {
case 'tlLeaderboard': return 'Tetra League leaderboard'; case 'tlLeaderboard': return 'Tetra League leaderboard';
case 'noRecords': return 'No records'; case 'noRecords': return 'No records';
case 'noRecord': return 'No record'; case 'noRecord': return 'No record';
case 'botRecord': return 'Bots are not allowed to set records';
case 'anonRecord': return 'Guests are not allowed to set records';
case 'notEnoughData': return 'Not enough data'; case 'notEnoughData': return 'Not enough data';
case 'noHistorySaved': return 'No history saved'; case 'noHistorySaved': return 'No history saved';
case 'obtainDate': return ({required Object date}) => 'Obtained ${date}'; case 'obtainDate': return ({required Object date}) => 'Obtained ${date}';
@ -1352,6 +1360,7 @@ extension on Translations {
case 'exactValue': return 'Exact value'; case 'exactValue': return 'Exact value';
case 'neverPlayedTL': return 'That user never played Tetra League'; case 'neverPlayedTL': return 'That user never played Tetra League';
case 'botTL': return 'Bots are not allowed to play Tetra League'; case 'botTL': return 'Bots are not allowed to play Tetra League';
case 'anonTL': return 'Guests are not allowed to play Tetra League';
case 'exportDB': return 'Export local database'; case 'exportDB': return 'Export local database';
case 'exportDBDescription': return 'It contains states and Tetra League records of the tracked players and list of tracked players.'; case 'exportDBDescription': return 'It contains states and Tetra League records of the tracked players and list of tracked players.';
case 'desktopExportAlertTitle': return 'Desktop export'; case 'desktopExportAlertTitle': return 'Desktop export';
@ -1830,6 +1839,8 @@ extension on _StringsRu {
case 'tlLeaderboard': return 'Рейтинговая таблица'; case 'tlLeaderboard': return 'Рейтинговая таблица';
case 'noRecords': return 'Нет записей'; case 'noRecords': return 'Нет записей';
case 'noRecord': return 'Нет рекорда'; case 'noRecord': return 'Нет рекорда';
case 'botRecord': return 'Ботам нельзя ставить рекорды';
case 'anonRecord': return 'Гостям нельзя ставить рекорды';
case 'notEnoughData': return 'Недостаточно данных'; case 'notEnoughData': return 'Недостаточно данных';
case 'noHistorySaved': return 'Нет сохранённой истории'; case 'noHistorySaved': return 'Нет сохранённой истории';
case 'obtainDate': return ({required Object date}) => 'Получено ${date}'; case 'obtainDate': return ({required Object date}) => 'Получено ${date}';
@ -1856,6 +1867,7 @@ extension on _StringsRu {
case 'exactValue': return 'Точное значение'; case 'exactValue': return 'Точное значение';
case 'neverPlayedTL': return 'Этот игрок никогда не играл в Тетра Лигу'; case 'neverPlayedTL': return 'Этот игрок никогда не играл в Тетра Лигу';
case 'botTL': return 'Ботам нельзя играть в Тетра Лигу'; case 'botTL': return 'Ботам нельзя играть в Тетра Лигу';
case 'anonTL': return 'Гостям нельзя играть в Тетра Лигу';
case 'exportDB': return 'Экспортировать локальную базу данных'; case 'exportDB': return 'Экспортировать локальную базу данных';
case 'exportDBDescription': return 'Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.'; case 'exportDBDescription': return 'Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.';
case 'desktopExportAlertTitle': return 'Экспорт на десктопе'; case 'desktopExportAlertTitle': return 'Экспорт на десктопе';

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/main.dart' show packageInfo; import 'package:tetra_stats/main.dart' show packageInfo;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:tetra_stats/services/custom_http_client.dart'; import 'package:tetra_stats/services/custom_http_client.dart';
@ -51,6 +52,15 @@ const String createTetrioTLRecordsTable = '''
) )
'''; ''';
const String createTetrioTLReplayStats = '''
CREATE TABLE "tetrioTLReplayStats" (
"id" TEXT NOT NULL,
"player1" TEXT NOT NULL,
"player2" TEXT NOT NULL,
PRIMARY KEY("id")
)
''';
class TetrioService extends DB { class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {}; Map<String, List<TetrioPlayer>> _players = {};
final Map<String, TetrioPlayer> _playersCache = {}; final Map<String, TetrioPlayer> _playersCache = {};
@ -110,19 +120,19 @@ class TetrioService extends DB {
} }
} }
Future<String> szyDownloadAndSaveReplay(String replayID) async { Future<List<dynamic>> szyGetReplay(String replayID) async {
Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID'); Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
var downloadPath = await getDownloadsDirectory(); var downloadPath = await getDownloadsDirectory();
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory(); downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
var replayFile = File("${downloadPath.path}/$replayID.ttrm"); var replayFile = File("${downloadPath.path}/$replayID.ttrm");
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist(); if (replayFile.existsSync()) return [replayFile.readAsStringSync(), replayFile.readAsBytesSync()];
try{ try{
final response = await client.get(url); final response = await client.get(url);
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
await replayFile.writeAsBytes(response.bodyBytes); developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
return replayFile.path; return [response.body, response.bodyBytes];
case 404: case 404:
throw SzyNotFound(); throw SzyNotFound();
case 403: case 403:
@ -137,7 +147,7 @@ class TetrioService extends DB {
case 504: case 504:
throw SzyInternalProblem(); throw SzyInternalProblem();
default: default:
developer.log("szyDownloadAndSaveReplay: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode); developer.log("szyDownload: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} on http.ClientException catch (e, s) { } on http.ClientException catch (e, s) {
@ -146,6 +156,22 @@ class TetrioService extends DB {
} }
} }
Future<String> SaveReplay(String replayID) async {
Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
var downloadPath = await getDownloadsDirectory();
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
var replay = await szyGetReplay(replayID);
await replayFile.writeAsBytes(replay[1]);
return replayFile.path;
}
Future<ReplayData> analyzeReplay(String replayID) async{
Map<String, dynamic> toAnalyze = jsonDecode((await szyGetReplay(replayID))[0]);
return ReplayData.fromJson(toAnalyze);
}
Future<double?> fetchTopTR(String id) async { Future<double?> fetchTopTR(String id) async {
try{ try{
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id); var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);

View File

@ -35,7 +35,7 @@ var chartsData = <DropdownMenuItem<List<FlSpot>>>[];
List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR"]; List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR"];
int _chartsIndex = 0; int _chartsIndex = 0;
final NumberFormat _timeInSec = NumberFormat("#,###.###s."); final NumberFormat _timeInSec = NumberFormat("#,###.###s.");
final NumberFormat _secs = NumberFormat("00.###"); final NumberFormat secs = NumberFormat("00.###");
final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
@ -55,7 +55,7 @@ Future<void> copyToClipboard(String text) async {
} }
String get40lTime(int microseconds){ String get40lTime(int microseconds){
return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(_secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000); return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000);
} }
class _MainState extends State<MainView> with SingleTickerProviderStateMixin { class _MainState extends State<MainView> with SingleTickerProviderStateMixin {

View File

@ -237,6 +237,7 @@ class SettingsState extends State<SettingsView> {
), ),
), ),
ListTile(title: Text("Customization"), ListTile(title: Text("Customization"),
subtitle: Text("I don't want to implement this"),
trailing: Icon(Icons.arrow_right), trailing: Icon(Icons.arrow_right),
onTap: () { onTap: () {
Navigator.pushNamed(context, "/customization"); Navigator.pushNamed(context, "/customization");

View File

@ -1,10 +1,11 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'dart:io'; import 'dart:io';
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy; import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
import 'package:tetra_stats/widgets/vs_graphs.dart'; import 'package:tetra_stats/widgets/vs_graphs.dart';
import 'main_view.dart' show teto; import 'main_view.dart' show teto, secs;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -21,6 +22,10 @@ int roundSelector = -1; // -1 = match averages, otherwise round number-1
List<DropdownMenuItem> rounds = []; // index zero will be match stats List<DropdownMenuItem> rounds = []; // index zero will be match stats
late String oldWindowTitle; late String oldWindowTitle;
Duration framesToTime(int frames){
return Duration(microseconds: frames~/6e-5);
}
class TlMatchResultView extends StatefulWidget { class TlMatchResultView extends StatefulWidget {
final TetraLeagueAlphaRecord record; final TetraLeagueAlphaRecord record;
final String initPlayerId; final String initPlayerId;
@ -33,12 +38,14 @@ class TlMatchResultView extends StatefulWidget {
class TlMatchResultState extends State<TlMatchResultView> { class TlMatchResultState extends State<TlMatchResultView> {
late ScrollController _scrollController; late ScrollController _scrollController;
late Future<ReplayData?> replayData;
@override @override
void initState(){ void initState(){
_scrollController = ScrollController(); _scrollController = ScrollController();
rounds = [DropdownMenuItem(value: -1, child: Text(t.match))]; rounds = [DropdownMenuItem(value: -1, child: Text(t.match))];
rounds.addAll([for (int i = 0; i < widget.record.endContext.first.secondaryTracking.length; i++) DropdownMenuItem(value: i, child: Text(t.roundNumber(n: i+1)))]); rounds.addAll([for (int i = 0; i < widget.record.endContext.first.secondaryTracking.length; i++) DropdownMenuItem(value: i, child: Text(t.roundNumber(n: i+1)))]);
replayData = teto.analyzeReplay(widget.record.replayId);
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){ if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
windowManager.getTitle().then((value) => oldWindowTitle = value); windowManager.getTitle().then((value) => oldWindowTitle = value);
windowManager.setTitle("Tetra Stats: ${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}"); windowManager.setTitle("Tetra Stats: ${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}");
@ -86,7 +93,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
//anchor.remove(); //anchor.remove();
} else{ } else{
try{ try{
String path = await teto.szyDownloadAndSaveReplay(widget.record.replayId); String path = await teto.SaveReplay(widget.record.replayId);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.replaySaved(path: path)))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.replaySaved(path: path))));
} on TetrioReplayAlreadyExist{ } on TetrioReplayAlreadyExist{
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.replayAlreadySaved))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.replayAlreadySaved)));
@ -189,6 +196,22 @@ class TlMatchResultState extends State<TlMatchResultView> {
), ),
), ),
), ),
SliverToBoxAdapter(child: FutureBuilder(future: replayData, builder: (context, snapshot) {
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return CircularProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
var time = framesToTime(snapshot.data!.totalLength);
return Center(child: Text("Match Length: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}"));
}else{
return Text("skill issue");
}
}
},),),
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: Divider(), child: Divider(),
) )
@ -219,6 +242,39 @@ class TlMatchResultState extends State<TlMatchResultView> {
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
FutureBuilder(future: replayData, builder: (BuildContext context, AsyncSnapshot<ReplayData?> snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return LinearProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
var greenSidePlayer = snapshot.data!.endcontext.indexWhere(((element) => element.userId == widget.initPlayerId));
var redSidePlayer = snapshot.data!.endcontext.indexWhere(((element) => element.userId != widget.initPlayerId));
return Column(children: [
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].piecesPlaced,
redSide: snapshot.data!.totalStats[redSidePlayer].piecesPlaced,
label: "Pieces Placed", higherIsBetter: true),
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].linesCleared,
redSide: snapshot.data!.totalStats[redSidePlayer].linesCleared,
label: "Lines Cleared", higherIsBetter: true),
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100,
redSide: snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100,
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].topCombo,
redSide: snapshot.data!.totalStats[redSidePlayer].topCombo,
label: "Best Combo", higherIsBetter: true),
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].topBtB,
redSide: snapshot.data!.totalStats[redSidePlayer].topBtB,
label: "Best BtB", higherIsBetter: true),
],);
}else{
return Text("skill issue");
}
}
})
], ],
), ),
const Divider(), const Divider(),
@ -356,12 +412,12 @@ class TlMatchResultState extends State<TlMatchResultView> {
CompareThingy( CompareThingy(
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.das, greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.das,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.das, redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.das,
label: "DAS", label: "DAS", fractionDigits: 1,
higherIsBetter: false), higherIsBetter: false),
CompareThingy( CompareThingy(
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.arr, greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.arr,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.arr, redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.arr,
label: "ARR", label: "ARR", fractionDigits: 1,
higherIsBetter: false), higherIsBetter: false),
CompareThingy( CompareThingy(
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.sdf, greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.sdf,

View File

@ -43,6 +43,8 @@
"tlLeaderboard": "Tetra League leaderboard", "tlLeaderboard": "Tetra League leaderboard",
"noRecords": "No records", "noRecords": "No records",
"noRecord": "No record", "noRecord": "No record",
"botRecord": "Bots are not allowed to set records",
"anonRecord": "Guests are not allowed to set records",
"notEnoughData": "Not enough data", "notEnoughData": "Not enough data",
"noHistorySaved": "No history saved", "noHistorySaved": "No history saved",
"obtainDate": "Obtained ${date}", "obtainDate": "Obtained ${date}",
@ -69,6 +71,7 @@
"exactValue": "Exact value", "exactValue": "Exact value",
"neverPlayedTL": "That user never played Tetra League", "neverPlayedTL": "That user never played Tetra League",
"botTL": "Bots are not allowed to play Tetra League", "botTL": "Bots are not allowed to play Tetra League",
"anonTL": "Guests are not allowed to play Tetra League",
"exportDB": "Export local database", "exportDB": "Export local database",
"exportDBDescription": "It contains states and Tetra League records of the tracked players and list of tracked players.", "exportDBDescription": "It contains states and Tetra League records of the tracked players and list of tracked players.",
"desktopExportAlertTitle": "Desktop export", "desktopExportAlertTitle": "Desktop export",

View File

@ -43,6 +43,8 @@
"tlLeaderboard": "Рейтинговая таблица", "tlLeaderboard": "Рейтинговая таблица",
"noRecords": "Нет записей", "noRecords": "Нет записей",
"noRecord": "Нет рекорда", "noRecord": "Нет рекорда",
"botRecord": "Ботам нельзя ставить рекорды",
"anonRecord": "Гостям нельзя ставить рекорды",
"notEnoughData": "Недостаточно данных", "notEnoughData": "Недостаточно данных",
"noHistorySaved": "Нет сохранённой истории", "noHistorySaved": "Нет сохранённой истории",
"obtainDate": "Получено ${date}", "obtainDate": "Получено ${date}",
@ -69,6 +71,7 @@
"exactValue": "Точное значение", "exactValue": "Точное значение",
"neverPlayedTL": "Этот игрок никогда не играл в Тетра Лигу", "neverPlayedTL": "Этот игрок никогда не играл в Тетра Лигу",
"botTL": "Ботам нельзя играть в Тетра Лигу", "botTL": "Ботам нельзя играть в Тетра Лигу",
"anonTL": "Гостям нельзя играть в Тетра Лигу",
"exportDB": "Экспортировать локальную базу данных", "exportDB": "Экспортировать локальную базу данных",
"exportDBDescription": "Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.", "exportDBDescription": "Она содержит состояния аккаунтов и их матчей в Тетра Лиге для отслеживаемых игроков и список таких игроков.",
"desktopExportAlertTitle": "Экспорт на десктопе", "desktopExportAlertTitle": "Экспорт на десктопе",