Spike counter + caching for szy api + more badges
ToDo: save advanced stats to the DB, polish UI, add interactivity to graphs
This commit is contained in:
parent
8dc2a5bced
commit
39569ffe0c
|
@ -5,6 +5,29 @@ import 'tetrio.dart';
|
|||
// I want to implement those fancy TWC stats
|
||||
// So, i'm going to read replay for things
|
||||
|
||||
int biggestSpikeFromReplay(events){
|
||||
int previousIGEeventFrame = -1;
|
||||
int spikeCounter = 0;
|
||||
int biggestSpike = 0;
|
||||
for (var event in events){
|
||||
if (event["type"] == "ige" && event["data"]["data"]["type"] == "interaction"){
|
||||
if (previousIGEeventFrame.isNegative){
|
||||
spikeCounter = event['data']['data']['data']['amt'];
|
||||
biggestSpike = spikeCounter;
|
||||
}else{
|
||||
if (event['data']['data']['frame'] - previousIGEeventFrame < 60){
|
||||
spikeCounter = spikeCounter + event['data']['data']['data']['amt'] as int;
|
||||
}else{
|
||||
spikeCounter = event['data']['data']['data']['amt'];
|
||||
}
|
||||
biggestSpike = max(biggestSpike, spikeCounter);
|
||||
}
|
||||
previousIGEeventFrame = event['data']['data']['frame'];
|
||||
}
|
||||
}
|
||||
return biggestSpike;
|
||||
}
|
||||
|
||||
class Garbage{ // charsys where???
|
||||
late int sent;
|
||||
late int recived;
|
||||
|
@ -43,11 +66,11 @@ class ReplayStats{
|
|||
late int score;
|
||||
late int topCombo;
|
||||
late int topBtB;
|
||||
late int topSpike;
|
||||
late int tspins;
|
||||
late Clears clears;
|
||||
late Garbage garbage;
|
||||
late Finesse finesse;
|
||||
late int kills;
|
||||
|
||||
double get finessePercentage => finesse.perfectPieces / piecesPlaced;
|
||||
|
||||
|
@ -60,14 +83,14 @@ class ReplayStats{
|
|||
required this.score,
|
||||
required this.topCombo,
|
||||
required this.topBtB,
|
||||
required this.topSpike,
|
||||
required this.tspins,
|
||||
required this.clears,
|
||||
required this.garbage,
|
||||
required this.finesse,
|
||||
required this.kills,
|
||||
});
|
||||
|
||||
ReplayStats.fromJson(Map<String, dynamic> json){
|
||||
ReplayStats.fromJson(Map<String, dynamic> json, int inputTopSpike){
|
||||
seed = json['seed'];
|
||||
linesCleared = json['lines'];
|
||||
piecesPlaced = json['piecesplaced'];
|
||||
|
@ -76,11 +99,11 @@ class ReplayStats{
|
|||
score = json['score'];
|
||||
topCombo = json['topcombo'];
|
||||
topBtB = json['topbtb'];
|
||||
topSpike = inputTopSpike;
|
||||
tspins = json['tspins'];
|
||||
clears = Clears.fromJson(json['clears']);
|
||||
garbage = Garbage.fromJson(json['garbage']);
|
||||
finesse = Finesse.fromJson(json['finesse']);
|
||||
kills = json['kills'];
|
||||
}
|
||||
|
||||
ReplayStats.createEmpty(){
|
||||
|
@ -92,11 +115,11 @@ class ReplayStats{
|
|||
score = 0;
|
||||
topCombo = 0;
|
||||
topBtB = 0;
|
||||
topSpike = 0;
|
||||
tspins = 0;
|
||||
clears = Clears(singles: 0, doubles: 0, triples: 0, quads: 0, pentas: 0, allClears: 0, tSpinZeros: 0, tSpinSingles: 0, tSpinDoubles: 0, tSpinTriples: 0, tSpinPentas: 0, tSpinQuads: 0, tSpinMiniZeros: 0, tSpinMiniSingles: 0, tSpinMiniDoubles: 0);
|
||||
garbage = Garbage(sent: 0, recived: 0, attack: 0, cleared: 0);
|
||||
finesse = Finesse(combo: 0, faults: 0, perfectPieces: 0);
|
||||
kills = 0;
|
||||
}
|
||||
|
||||
ReplayStats operator + (ReplayStats other){
|
||||
|
@ -108,11 +131,12 @@ class ReplayStats{
|
|||
score: score + other.score,
|
||||
topCombo: max(topCombo, other.topCombo),
|
||||
topBtB: max(topBtB, other.topBtB),
|
||||
topSpike: max(topSpike, other.topSpike),
|
||||
tspins: tspins + other.tspins,
|
||||
clears: clears + other.clears,
|
||||
garbage: garbage + other.garbage,
|
||||
finesse: finesse + other.finesse,
|
||||
kills: kills + other.kills);
|
||||
finesse: finesse + other.finesse
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,6 +146,7 @@ class ReplayData{
|
|||
late List<EndContextMulti> endcontext;
|
||||
late List<List<ReplayStats>> stats;
|
||||
late List<ReplayStats> totalStats;
|
||||
late List<List<String>> roundWinners;
|
||||
late List<int> roundLengths; // in frames
|
||||
late int totalLength; // in frames
|
||||
|
||||
|
@ -130,6 +155,7 @@ class ReplayData{
|
|||
required this.endcontext,
|
||||
required this.stats,
|
||||
required this.totalStats,
|
||||
required this.roundWinners,
|
||||
required this.roundLengths,
|
||||
required this.totalLength
|
||||
}){
|
||||
|
@ -143,24 +169,20 @@ class ReplayData{
|
|||
roundLengths = [];
|
||||
totalLength = 0;
|
||||
stats = [];
|
||||
roundWinners = [];
|
||||
totalStats = [ReplayStats.createEmpty(), ReplayStats.createEmpty()];
|
||||
|
||||
int firstInEndContext = json["data"][0]["board"].indexWhere((element) => element["id"] == endcontext[0].userId);
|
||||
int secondInEndContext = json["data"][0]["board"].indexWhere((element) => element["id"] == endcontext[1].userId);
|
||||
for(var round in json['data']) {
|
||||
roundLengths.add(max(round['replays'][0]['frames'], round['replays'][1]['frames']));
|
||||
totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']);
|
||||
ReplayStats playerOne = ReplayStats.fromJson(round['replays'][0]['events'].last['data']['export']['stats']);
|
||||
ReplayStats playerTwo = ReplayStats.fromJson(round['replays'][1]['events'].last['data']['export']['stats']);
|
||||
int winner = round['board'].indexWhere((element) => element["success"] == true);
|
||||
roundWinners.add([round['board'][winner]["id"], round['board'][winner]["username"]]);
|
||||
ReplayStats playerOne = ReplayStats.fromJson(round['replays'][firstInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][secondInEndContext]['events'])); // (events contain recived attacks)
|
||||
ReplayStats playerTwo = ReplayStats.fromJson(round['replays'][secondInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][firstInEndContext]['events']));
|
||||
stats.add([playerOne, playerTwo]);
|
||||
totalStats[0] = totalStats[0] + playerOne;
|
||||
totalStats[1] = totalStats[1] + playerTwo;
|
||||
// print(round['replays'][0]['events'].last['data']['export']['stats']);
|
||||
// for (var event in round['replays'][0]['events']){
|
||||
// if (event["type"] == "ige" && event["data"]["data"]["type"] == "interaction_confirm"){
|
||||
// print(event['data']["data"]["data"]); // lol
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// Сами по себе эти ивенты ничего не дают,
|
||||
// Хотя можно попробовать вычислить biggest spike
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ class TetrioService extends DB {
|
|||
Map<String, List<TetrioPlayer>> _players = {};
|
||||
final Map<String, TetrioPlayer> _playersCache = {};
|
||||
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
||||
final Map<String, dynamic> _replaysCache = {};
|
||||
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
|
||||
final Map<String, List<News>> _newsCache = {};
|
||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||
|
@ -121,6 +122,12 @@ class TetrioService extends DB {
|
|||
}
|
||||
|
||||
Future<List<dynamic>> szyGetReplay(String replayID) async {
|
||||
try{
|
||||
var cached = _replaysCache.entries.firstWhere((element) => element.key == replayID);
|
||||
return cached.value;
|
||||
}catch (e){
|
||||
// actually going to obtain
|
||||
}
|
||||
Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
||||
var downloadPath = await getDownloadsDirectory();
|
||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||
|
@ -132,6 +139,7 @@ class TetrioService extends DB {
|
|||
switch (response.statusCode) {
|
||||
case 200:
|
||||
developer.log("szyDownload: Replay downloaded", name: "services/tetrio_crud", error: response.statusCode);
|
||||
_replaysCache[replayID] = [response.body, response.bodyBytes];
|
||||
return [response.body, response.bodyBytes];
|
||||
case 404:
|
||||
throw SzyNotFound();
|
||||
|
@ -157,7 +165,6 @@ 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");
|
||||
|
|
|
@ -801,6 +801,7 @@ class CompareThingy extends StatelessWidget {
|
|||
final bool higherIsBetter;
|
||||
final int? fractionDigits;
|
||||
final String? postfix;
|
||||
final String? prefix;
|
||||
const CompareThingy(
|
||||
{super.key,
|
||||
required this.greenSide,
|
||||
|
@ -808,6 +809,7 @@ class CompareThingy extends StatelessWidget {
|
|||
required this.label,
|
||||
required this.higherIsBetter,
|
||||
this.fractionDigits,
|
||||
this.prefix,
|
||||
this.postfix});
|
||||
|
||||
String verdict(num greenSide, num redSide, int fraction) {
|
||||
|
@ -845,7 +847,7 @@ class CompareThingy extends StatelessWidget {
|
|||
],
|
||||
)),
|
||||
child: Text(
|
||||
f.format(greenSide) + (postfix ?? ""),
|
||||
(prefix ?? "") + f.format(greenSide) + (postfix ?? ""),
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
shadows: <Shadow>[
|
||||
|
@ -899,7 +901,7 @@ class CompareThingy extends StatelessWidget {
|
|||
],
|
||||
)),
|
||||
child: Text(
|
||||
f.format(redSide) + (postfix ?? ""),
|
||||
(prefix ?? "") + f.format(redSide) + (postfix ?? ""),
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
shadows: <Shadow>[
|
||||
|
|
|
@ -269,10 +269,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
value: "history",
|
||||
child: Text(t.fetchAndsaveTLHistory),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: "test",
|
||||
child: Text("Test replay reading"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: "/states",
|
||||
child: Text(t.showStoredData),
|
||||
|
@ -294,11 +290,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
case "history":
|
||||
changePlayer(_searchFor, fetchHistory: true);
|
||||
break;
|
||||
case "test":
|
||||
var replayfile = File("/home/dan63047/Загрузки/659337dd1eef65e513c5dc8d.ttrm").readAsStringSync();
|
||||
var testObject = ReplayData.fromJson(jsonDecode(replayfile));
|
||||
print("lol");
|
||||
break;
|
||||
default:
|
||||
context.go(value);
|
||||
}
|
||||
|
|
|
@ -201,13 +201,18 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
|||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
return CircularProgressIndicator();
|
||||
return const LinearProgressIndicator();
|
||||
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)}"));
|
||||
if (roundSelector.isNegative){
|
||||
var time = framesToTime(snapshot.data!.totalLength);
|
||||
return Center(child: Text("Match Length: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}", textAlign: TextAlign.center));
|
||||
}else{
|
||||
var time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
|
||||
return Center(child: Text("Round Length: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\nWinner: ${snapshot.data!.roundWinners[roundSelector][1]}", textAlign: TextAlign.center,));
|
||||
}
|
||||
}else{
|
||||
return Text("skill issue");
|
||||
return const Text("skill issue", textAlign: TextAlign.center);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -247,27 +252,47 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
|||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
return LinearProgressIndicator();
|
||||
return const 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));
|
||||
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,
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][greenSidePlayer].piecesPlaced,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][redSidePlayer].piecesPlaced,
|
||||
label: "Pieces Placed", higherIsBetter: true),
|
||||
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].linesCleared,
|
||||
redSide: snapshot.data!.totalStats[redSidePlayer].linesCleared,
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][greenSidePlayer].linesCleared,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][redSidePlayer].linesCleared,
|
||||
label: "Lines Cleared", higherIsBetter: true),
|
||||
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100,
|
||||
redSide: snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100,
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][greenSidePlayer].finessePercentage * 100,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][redSidePlayer].finessePercentage * 100,
|
||||
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
|
||||
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].topCombo,
|
||||
redSide: snapshot.data!.totalStats[redSidePlayer].topCombo,
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topSpike : snapshot.data!.stats[roundSelector][greenSidePlayer].topSpike,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topSpike : snapshot.data!.stats[roundSelector][redSidePlayer].topSpike,
|
||||
label: "Best Spike", higherIsBetter: true),
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topCombo : snapshot.data!.stats[roundSelector][greenSidePlayer].topCombo,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topCombo : snapshot.data!.stats[roundSelector][redSidePlayer].topCombo,
|
||||
label: "Best Combo", higherIsBetter: true),
|
||||
CompareThingy(greenSide: snapshot.data!.totalStats[greenSidePlayer].topBtB,
|
||||
redSide: snapshot.data!.totalStats[redSidePlayer].topBtB,
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topBtB : snapshot.data!.stats[roundSelector][greenSidePlayer].topBtB,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topBtB : snapshot.data!.stats[roundSelector][redSidePlayer].topBtB,
|
||||
label: "Best BtB", higherIsBetter: true),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Text("Garbage", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
|
||||
),
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.sent,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.sent,
|
||||
label: "Sent", higherIsBetter: true),
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.recived,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.recived,
|
||||
label: "Recived", higherIsBetter: true),
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.attack,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.attack,
|
||||
label: "Attack", higherIsBetter: true),
|
||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.cleared,
|
||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.cleared,
|
||||
label: "Cleared", higherIsBetter: true),
|
||||
],);
|
||||
}else{
|
||||
return Text("skill issue");
|
||||
|
@ -412,17 +437,17 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
|||
CompareThingy(
|
||||
greenSide: 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", fractionDigits: 1,
|
||||
label: "DAS", fractionDigits: 1, postfix: "F",
|
||||
higherIsBetter: false),
|
||||
CompareThingy(
|
||||
greenSide: 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", fractionDigits: 1,
|
||||
label: "ARR", fractionDigits: 1, postfix: "F",
|
||||
higherIsBetter: false),
|
||||
CompareThingy(
|
||||
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.sdf,
|
||||
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.sdf,
|
||||
label: "SDF",
|
||||
label: "SDF", prefix: "x",
|
||||
higherIsBetter: true),
|
||||
CompareBoolThingy(
|
||||
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.safeLock,
|
||||
|
|
|
@ -166,6 +166,7 @@ flutter:
|
|||
- res/tetrio_badges/sfu_raccoon_1.png
|
||||
- res/tetrio_badges/sfu_raccoon_2.png
|
||||
- res/tetrio_badges/sfu_raccoon_3.png
|
||||
- res/tetrio_badges/streamersuperlobby.png
|
||||
- res/tetrio_badges/superlobby.png
|
||||
- res/tetrio_badges/superlobby2.png
|
||||
- res/tetrio_badges/taws_u50_1.png
|
||||
|
@ -185,6 +186,10 @@ flutter:
|
|||
- res/tetrio_badges/ttsdtc_1.png
|
||||
- res/tetrio_badges/ttsdtc_2.png
|
||||
- res/tetrio_badges/ttsdtc_3.png
|
||||
- res/tetrio_badges/twc23_1.png
|
||||
- res/tetrio_badges/twc23_2.png
|
||||
- res/tetrio_badges/twc23_3.png
|
||||
- res/tetrio_badges/twc23_4.png
|
||||
- res/tetrio_badges/ubcea_1.png
|
||||
- res/tetrio_badges/ubcea_2.png
|
||||
- res/tetrio_badges/ubcea_3.png
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Loading…
Reference in New Issue