Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ std::string Analysis::asJson() {
ss << JUIN(1,"button_count", ap[p].button_count) << ",\n";
ss << JUIN(1,"cstick_count", ap[p].cstick_count) << ",\n";
ss << JUIN(1,"astick_count", ap[p].astick_count) << ",\n";
ss << JUIN(1,"trigger_count", ap[p].trigger_count) << ",\n";
ss << JFLT(1,"actions_per_min", ap[p].apm) << ",\n";
ss << JUIN(1,"state_changes", ap[p].state_changes) << ",\n";
ss << JFLT(1,"states_per_min", ap[p].aspm) << ",\n";
Expand Down
6 changes: 4 additions & 2 deletions src/analysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,10 @@ struct AnalysisPlayer {
unsigned max_galint = 0; //Maximum GALINT frames after a ledgedash
unsigned button_count = 0; //Button presses
unsigned cstick_count = 0; //C stick movements
unsigned astick_count = 0; //Action state changes
unsigned astick_count = 0; //Analog stick movements
unsigned trigger_count = 0; //Analog trigger movements
float apm = 0; //Actions per minute (combines buttons and csticks)
unsigned state_changes = 0; //Analog stick movements
unsigned state_changes = 0; //Action state changes
float aspm = 0; //Action states per minute

unsigned used_throws = 0; //Number of throws we threw out
Expand Down Expand Up @@ -169,6 +170,7 @@ struct Analysis {
std::string stage_name = ""; //Readable name of the stage
int winner_port = 0; //Port index of the winning player (-1 == no winner)
unsigned game_length = 0; //Length of the game in frames (0 == internal frame -123)
unsigned game_length_playable = 0; //Length of the game only counting playable frames
unsigned timer = 0; //Game timer starting minutes
AnalysisPlayer* ap; //Analysis of individual players in the game
unsigned* dynamics; //Interaction dynamics on a per-frame basis
Expand Down
19 changes: 15 additions & 4 deletions src/analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void Analyzer::getBasicGameInfo(const SlippiReplay &s, Analysis* a) const {
a->parse_errors = s.errors;
a->game_time = s.start_time;
a->game_length = s.frame_count;
a->game_length_playable = s.frame_count + LOAD_FRAME - PLAYABLE_FRAME;
a->timer = s.timer;
a->end_type = s.end_type;
a->lras_player = s.lras;
Expand Down Expand Up @@ -140,6 +141,8 @@ void Analyzer::countButtons(const SlippiReplay &s, Analysis *a) const {
float last_ay = 0;
float last_cx = 0;
float last_cy = 0;
float last_trigger_l = 0;
float last_trigger_r = 0;
for(unsigned f = (-LOAD_FRAME); f < s.frame_count; ++f) {
// Add buttons pressed this frame to button count
uint16_t cur_buttons = p.frame[f].buttons;
Expand All @@ -161,6 +164,14 @@ void Analyzer::countButtons(const SlippiReplay &s, Analysis *a) const {
a->ap[pi].cstick_count += checkStickMovement(cur_cx,cur_cy,last_cx,last_cy);
last_cx = cur_cx;
last_cy = cur_cy;

// Check analog trigger for movement
float cur_trigger_l = p.frame[f].phys_l;
float cur_trigger_r = p.frame[f].phys_r;
a->ap[pi].trigger_count += checkTriggerMovement(cur_trigger_l, last_trigger_l);
a->ap[pi].trigger_count += checkTriggerMovement(cur_trigger_r, last_trigger_r);
last_trigger_l = cur_trigger_l;
last_trigger_r = cur_trigger_r;
}
}
}
Expand Down Expand Up @@ -1065,12 +1076,12 @@ void Analyzer::computeTrivialInfo(const SlippiReplay &s, Analysis *a) const {
}

// Get actions per minute
unsigned total_actions =
a->ap[pi].button_count + a->ap[pi].cstick_count + a->ap[pi].astick_count;
a->ap[pi].apm = total_actions * (3600.0f / a->game_length);
unsigned total_actions = a->ap[pi].button_count + a->ap[pi].cstick_count
+ a->ap[pi].astick_count + a->ap[pi].trigger_count;
a->ap[pi].apm = total_actions * (3600.0f / a->game_length_playable);

// Get action states per minute
a->ap[pi].aspm = a->ap[pi].state_changes * (3600.0f / a->game_length);
a->ap[pi].aspm = a->ap[pi].state_changes * (3600.0f / a->game_length_playable);

// Get total number of moves landed and move accuracy
a->ap[pi].total_moves_landed = 0;
Expand Down
73 changes: 51 additions & 22 deletions src/analyzer.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#ifndef ANALYZER_H_
#define ANALYZER_H_

#include <iostream>
#include <fstream>
#include <math.h> //sqrt

#include "enums.h"
#include "util.h"
#include "replay.h"
#include "analysis.h"

Expand All @@ -19,6 +16,18 @@ const unsigned MAX_WAIT = 15; //If we don't act out of wait or stun for

namespace slip {

enum JoystickRegion {
DZ = 0,
NE = 1,
SE = 2,
SW = 3,
NW = 4,
N = 5,
E = 6,
S = 7,
W = 8,
};

class Analyzer {
private:
int _debug; //Current debug level
Expand Down Expand Up @@ -304,28 +313,48 @@ class Analyzer {
static inline bool isDead(const SlippiFrame &f) {
return (f.flags_5 & 0x10) || f.action_pre < Action::Sleep;
}
static inline unsigned checkStickMovement(float x1, float y1, float x2, float y2, float neut=0.1f) {
// If a stick crossed an axis, that's a movement
if (x1 < 0 && x2 > 0) { return 1; }
if (x1 > 0 && x2 < 0) { return 1; }
if (y1 < 0 && y2 > 0) { return 1; }
if (y1 > 0 && y2 < 0) { return 1; }
static inline JoystickRegion getJoystickRegion(float x, float y, float neut) {
if (x >= neut && y >= neut) {
return JoystickRegion::NE;
}
else if (x >= neut && y <= -neut) {
return JoystickRegion::SE;
}
else if (x <= -neut && y <= -neut) {
return JoystickRegion::SW;
}
else if (x <= -neut && y >= neut) {
return JoystickRegion::NW;
}
else if (y >= neut) {
return JoystickRegion::N;
}
else if (x >= neut) {
return JoystickRegion::E;
}
else if (y <= -neut) {
return JoystickRegion::S;
}
else if (x <= -neut) {
return JoystickRegion::W;
}

return JoystickRegion::DZ;
}
static inline unsigned checkStickMovement(float x1, float y1, float x2, float y2, float neut=0.2875) {
// logic copied from slippi-js
// https://github.com/project-slippi/slippi-js/blob/master/src/common/stats/inputs.ts#L97

// If the higher absolute magnitude between x and y shifted,
// that's a movement
float ax1 = (x1 > 0) ? x1 : -x1;
float ax2 = (x2 > 0) ? x2 : -x2;
float ay1 = (y1 > 0) ? y1 : -y1;
float ay2 = (y2 > 0) ? y2 : -y2;
if (ax1 > ay1 && ax2 < ay2) { return 1; }
if (ax1 < ay1 && ax2 > ay2) { return 1; }
auto region1 = getJoystickRegion(x1, y1, neut);
auto region2 = getJoystickRegion(x2, y2, neut);

// If we moved from neutral to non neutral, that's a movement
if (ax1 < neut && ay1 < neut) {
if (ax2 > neut || ay2 > neut) { return 1; }
}
return region1 != JoystickRegion::DZ && region1 != region2 ? 1 : 0;
}
static inline unsigned checkTriggerMovement(float trigCurrent, float trigLast, float threshold = 0.3) {
// logic copied from slippi-js
// https://github.com/project-slippi/slippi-js/blob/master/src/common/stats/inputs.ts#L110

return 0;
return trigLast < threshold && trigCurrent >= threshold ? 1 : 0;
}
static inline std::string frameAsTimer(unsigned fnum, unsigned startmins) {
if (startmins == 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ namespace slip {
}
if (_replay.errors == 0) {
DOUT1(" Successfully parsed replay!");
return true;
} else {
WARN(" Replay parsed with " << _replay.errors << " errors");
return false;
}
return true;
}

bool Parser::_parseHeader() {
Expand Down