slp-enforcer
Version:
Finds violations of the Melee Controller Ruleset by inspecting SLP files
278 lines (277 loc) • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
var SDIRegion;
(function (SDIRegion) {
SDIRegion[SDIRegion["DZ"] = 0] = "DZ";
SDIRegion[SDIRegion["NE"] = 1] = "NE";
SDIRegion[SDIRegion["SE"] = 2] = "SE";
SDIRegion[SDIRegion["SW"] = 3] = "SW";
SDIRegion[SDIRegion["NW"] = 4] = "NW";
SDIRegion[SDIRegion["N"] = 5] = "N";
SDIRegion[SDIRegion["E"] = 6] = "E";
SDIRegion[SDIRegion["S"] = 7] = "S";
SDIRegion[SDIRegion["W"] = 8] = "W";
SDIRegion[SDIRegion["TILT"] = 9] = "TILT";
})(SDIRegion = exports.SDIRegion || (exports.SDIRegion = {}));
const DIAGONALS = [SDIRegion.NE, SDIRegion.SE, SDIRegion.NW, SDIRegion.SW];
const CARDINALS = [SDIRegion.N, SDIRegion.E, SDIRegion.S, SDIRegion.W];
function getSDIRegion(x, y) {
if (Math.abs(x) <= 0.2875 && Math.abs(y) <= 0.2875) {
return SDIRegion.DZ;
}
let magnitude = Math.sqrt(x ** 2 + y ** 2);
if (x >= 0.2875 && y >= 0.2875 && magnitude >= 0.7) {
return SDIRegion.NE;
}
if (x >= 0.2875 && y <= -0.2875 && magnitude >= 0.7) {
return SDIRegion.SE;
}
if (x <= -0.2875 && y <= -0.2875 && magnitude >= 0.7) {
return SDIRegion.SW;
}
if (x <= -0.2875 && y >= 0.2875 && magnitude >= 0.7) {
return SDIRegion.NW;
}
// Magnitude must be >=0.7 for these 4 if the condition is true. No need to check it
if (y >= 0.7) {
return SDIRegion.N;
}
if (x >= 0.7) {
return SDIRegion.E;
}
if (y <= -0.7) {
return SDIRegion.S;
}
if (x <= -0.7) {
return SDIRegion.W;
}
// Only place left
return SDIRegion.TILT;
}
exports.getSDIRegion = getSDIRegion;
// Directly adjacent
function isRegionAdjacent(regionA, regionB) {
if (regionA == SDIRegion.N) {
if (regionB == SDIRegion.NW || regionB == SDIRegion.NE) {
return true;
}
}
if (regionA == SDIRegion.NE) {
if (regionB == SDIRegion.N || regionB == SDIRegion.E) {
return true;
}
}
if (regionA == SDIRegion.E) {
if (regionB == SDIRegion.NE || regionB == SDIRegion.SE) {
return true;
}
}
if (regionA == SDIRegion.SE) {
if (regionB == SDIRegion.E || regionB == SDIRegion.S) {
return true;
}
}
if (regionA == SDIRegion.S) {
if (regionB == SDIRegion.SE || regionB == SDIRegion.SW) {
return true;
}
}
if (regionA == SDIRegion.SW) {
if (regionB == SDIRegion.S || regionB == SDIRegion.W) {
return true;
}
}
if (regionA == SDIRegion.W) {
if (regionB == SDIRegion.SW || regionB == SDIRegion.NW) {
return true;
}
}
if (regionA == SDIRegion.NW) {
if (regionB == SDIRegion.W || regionB == SDIRegion.N) {
return true;
}
}
return false;
}
exports.isRegionAdjacent = isRegionAdjacent;
// Just for diagonals, ie: skipping the cardinals
function isDiagonalAdjacent(regionA, regionB) {
const DIAGONALS = [SDIRegion.NE, SDIRegion.SE, SDIRegion.NW, SDIRegion.SW];
if (!DIAGONALS.includes(regionA)) {
return false;
}
if (!DIAGONALS.includes(regionB)) {
return false;
}
if (regionA == SDIRegion.NE) {
if (regionB == SDIRegion.NW || regionB == SDIRegion.SE) {
return true;
}
}
if (regionA == SDIRegion.NW) {
if (regionB == SDIRegion.NE || regionB == SDIRegion.SW) {
return true;
}
}
if (regionA == SDIRegion.SW) {
if (regionB == SDIRegion.SE || regionB == SDIRegion.NW) {
return true;
}
}
if (regionA == SDIRegion.SE) {
if (regionB == SDIRegion.NE || regionB == SDIRegion.SW) {
return true;
}
}
return false;
}
exports.isDiagonalAdjacent = isDiagonalAdjacent;
function countUniqueCoordinates(coords) {
const uniqueCoords = new Set();
for (const coord of coords) {
uniqueCoords.add(`${coord.x},${coord.y}`);
}
return uniqueCoords.size;
}
// Rapidly tapping the same direction and returning to neutral faster than once every 5.5 frames triggers 1 SDI and ignores subsequent attempts.
// Returns: array of violations
function failsSDIRuleOne(coords) {
// Pull out the region of every input
let violations = [];
let regions = [];
for (let coord of coords) {
regions.push(getSDIRegion(coord.x, coord.y));
}
for (let [i, region] of regions.entries()) {
// Look ahead 10 frames to see if we hit two SDIs from neutral
if (region === SDIRegion.DZ) {
let lastRegion = SDIRegion.DZ;
let firstSDIRegion = null;
let lastSDIFrame = -1000;
let consecutiveTiltFrames = 0;
let hasTouchedDZ = true; // You have to touch the deadzone in order for an SDI to count
for (let j = 1; j <= 9 && (i + j) < regions.length; j++) {
if (regions[i + j] === SDIRegion.DZ) {
hasTouchedDZ = true;
}
if (regions[i + j] === SDIRegion.TILT) {
consecutiveTiltFrames += 1;
}
else {
// Get the first SDI region
if (regions[i + j] !== SDIRegion.DZ && firstSDIRegion === null) {
firstSDIRegion = regions[i + j];
}
}
// If we went from DZ to the first SDI region.
// And we also haven't spent more than 3 consecutive frames in the tilt zone
if (hasTouchedDZ && [SDIRegion.DZ, SDIRegion.TILT].includes(lastRegion) && regions[i + j] === firstSDIRegion && consecutiveTiltFrames <= 3) {
if (i + j <= lastSDIFrame + 4) {
// This is a hack to be lenient as long as there's travel time
if (countUniqueCoordinates(coords.slice(i, i + j)) <= 2) {
// Two SDI frames were less than 5 frames away from each other!
violations.push(new index_1.Violation(i, "Failed SDI rule #1", coords.slice(i, i + 10)));
}
}
lastSDIFrame = i + j;
hasTouchedDZ = false; // Reset the DZ counter
}
lastRegion = regions[i + j];
if (regions[i + j] !== SDIRegion.TILT) {
consecutiveTiltFrames = 0;
}
}
}
}
return violations;
}
exports.failsSDIRuleOne = failsSDIRuleOne;
// Rapidly tapping the same diagonal and returning to an adjacent cardinal faster than once every 5.5 frames triggers 1 SDI and ignores subsequent attempts.
// Returns: array of violations
function failsSDIRuleTwo(coords) {
// Pull out the region of every input
let violations = [];
let regions = [];
for (let coord of coords) {
regions.push(getSDIRegion(coord.x, coord.y));
}
for (let i = 0; i < regions.length; i++) {
const startingRegion = regions[i];
// Start from a cardinal
if (!CARDINALS.includes(startingRegion)) {
continue;
}
// Now look 4 frames ahead.
// Do we alternate between here and an adjacent diagonal twice?
let sdiCount = 0;
let adjacentCardinalRegion = -1;
for (let j = 1; j <= 4 && (i + j) < regions.length; j++) {
// Ignore if we haven't moved regions
if (regions[i + j] === regions[i + j - 1]) {
continue;
}
// Have we hit the diagonal SDI? (an adjacent diagonal)
if (isRegionAdjacent(startingRegion, regions[i + j]) && DIAGONALS.includes(regions[i + j])) {
if (adjacentCardinalRegion === -1 || adjacentCardinalRegion === regions[i + j]) {
adjacentCardinalRegion = regions[i + j];
sdiCount++;
}
}
}
if (sdiCount >= 2) {
violations.push(new index_1.Violation(i, "Failed SDI rule #2", coords.slice(i, i + 5)));
}
}
return violations;
}
exports.failsSDIRuleTwo = failsSDIRuleTwo;
// Alternating between adjacent cardinals
// Returns: array of violations
function failsSDIRuleThree(coords) {
// Pull out the region of every input
let violations = [];
let regions = [];
for (let coord of coords) {
regions.push(getSDIRegion(coord.x, coord.y));
}
for (let i = 0; i < regions.length; i++) {
const currentRegion = regions[i];
if (DIAGONALS.includes(currentRegion)) {
// Look forward 4 frames to see if it goes to an adjacent diagonal and back
var hitAdjacent = false;
for (let j = i + 1; j <= i + 4 && j < regions.length; j++) {
// Hit the adjacent
if (isDiagonalAdjacent(regions[j], currentRegion)) {
hitAdjacent = true;
}
// Then returned back
if (hitAdjacent && (regions[j] === currentRegion)) {
violations.push(new index_1.Violation(i, "Failed SDI rule #3", coords.slice(i, i + 5)));
}
}
}
}
return violations;
}
exports.failsSDIRuleThree = failsSDIRuleThree;
function hasIllegalSDI(game, playerIndex, coords) {
// If we're on analog, then it always passes
if (!index_1.isBoxController(coords)) {
return new index_1.CheckResult(false);
}
let infractionFrames = failsSDIRuleOne(coords);
if (infractionFrames.length > 0) {
return new index_1.CheckResult(true, infractionFrames);
}
infractionFrames = failsSDIRuleTwo(coords);
if (infractionFrames.length > 0) {
return new index_1.CheckResult(true, infractionFrames);
}
infractionFrames = failsSDIRuleThree(coords);
if (infractionFrames.length > 0) {
return new index_1.CheckResult(true, infractionFrames);
}
return new index_1.CheckResult(false);
}
exports.hasIllegalSDI = hasIllegalSDI;