slp-enforcer
Version:
Finds violations of the Melee Controller Ruleset by inspecting SLP files
118 lines (117 loc) • 6.29 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const first_1 = __importDefault(require("lodash/first"));
const flatten_1 = __importDefault(require("lodash/flatten"));
const get_1 = __importDefault(require("lodash/get"));
const groupBy_1 = __importDefault(require("lodash/groupBy"));
const keyBy_1 = __importDefault(require("lodash/keyBy"));
const last_1 = __importDefault(require("lodash/last"));
const mapValues_1 = __importDefault(require("lodash/mapValues"));
const zip_1 = __importDefault(require("lodash/zip"));
function generateOverallStats({ settings, inputs, conversions, playableFrameCount, }) {
const inputsByPlayer = keyBy_1.default(inputs, "playerIndex");
const originalConversions = conversions;
const conversionsByPlayer = groupBy_1.default(conversions, (conv) => { var _a; return (_a = conv.moves[0]) === null || _a === void 0 ? void 0 : _a.playerIndex; });
const conversionsByPlayerByOpening = mapValues_1.default(conversionsByPlayer, (conversions) => groupBy_1.default(conversions, "openingType"));
const gameMinutes = playableFrameCount / 3600;
const overall = settings.players.map((player) => {
const playerIndex = player.playerIndex;
const playerInputs = get_1.default(inputsByPlayer, playerIndex) || {};
const inputCounts = {
buttons: get_1.default(playerInputs, "buttonInputCount"),
triggers: get_1.default(playerInputs, "triggerInputCount"),
cstick: get_1.default(playerInputs, "cstickInputCount"),
joystick: get_1.default(playerInputs, "joystickInputCount"),
total: get_1.default(playerInputs, "inputCount"),
};
// const conversions = get(conversionsByPlayer, playerIndex) || [];
// const successfulConversions = conversions.filter((conversion) => conversion.moves.length > 1);
let conversionCount = 0;
let successfulConversionCount = 0;
const opponentIndices = settings.players
.filter((opp) => {
// We want players which aren't ourselves
if (opp.playerIndex === playerIndex) {
return false;
}
// Make sure they're not on our team either
return !settings.isTeams || opp.teamId !== player.teamId;
})
.map((opp) => opp.playerIndex);
let totalDamage = 0;
let killCount = 0;
// These are the conversions that we did on our opponents
originalConversions
// Filter down to conversions of our opponent
.filter((conversion) => conversion.playerIndex !== playerIndex)
.forEach((conversion) => {
conversionCount++;
// We killed the opponent
if (conversion.didKill && conversion.lastHitBy === playerIndex) {
killCount += 1;
}
if (conversion.moves.length > 1 && conversion.moves[0].playerIndex === playerIndex) {
successfulConversionCount++;
}
conversion.moves.forEach((move) => {
if (move.playerIndex === playerIndex) {
totalDamage += move.damage;
}
});
});
return {
playerIndex: playerIndex,
inputCounts: inputCounts,
conversionCount: conversionCount,
totalDamage: totalDamage,
killCount: killCount,
successfulConversions: getRatio(successfulConversionCount, conversionCount),
inputsPerMinute: getRatio(inputCounts.total, gameMinutes),
digitalInputsPerMinute: getRatio(inputCounts.buttons, gameMinutes),
openingsPerKill: getRatio(conversionCount, killCount),
damagePerOpening: getRatio(totalDamage, conversionCount),
neutralWinRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices, "neutral-win"),
counterHitRatio: getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices, "counter-attack"),
beneficialTradeRatio: getBeneficialTradeRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices),
};
});
return overall;
}
exports.generateOverallStats = generateOverallStats;
function getRatio(count, total) {
return {
count: count,
total: total,
ratio: total ? count / total : null,
};
}
function getOpeningRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices, type) {
const openings = get_1.default(conversionsByPlayerByOpening, [playerIndex, type]) || [];
const opponentOpenings = flatten_1.default(opponentIndices.map((opponentIndex) => get_1.default(conversionsByPlayerByOpening, [opponentIndex, type]) || []));
return getRatio(openings.length, openings.length + opponentOpenings.length);
}
function getBeneficialTradeRatio(conversionsByPlayerByOpening, playerIndex, opponentIndices) {
const playerTrades = get_1.default(conversionsByPlayerByOpening, [playerIndex, "trade"]) || [];
const opponentTrades = flatten_1.default(opponentIndices.map((opponentIndex) => get_1.default(conversionsByPlayerByOpening, [opponentIndex, "trade"]) || []));
const benefitsPlayer = [];
// Figure out which punishes benefited this player
const zippedTrades = zip_1.default(playerTrades, opponentTrades);
zippedTrades.forEach((conversionPair) => {
const playerConversion = first_1.default(conversionPair);
const opponentConversion = last_1.default(conversionPair);
if (playerConversion && opponentConversion) {
const playerDamage = playerConversion.currentPercent - playerConversion.startPercent;
const opponentDamage = opponentConversion.currentPercent - opponentConversion.startPercent;
if (playerConversion.didKill && !opponentConversion.didKill) {
benefitsPlayer.push(playerConversion);
}
else if (playerDamage > opponentDamage) {
benefitsPlayer.push(playerConversion);
}
}
});
return getRatio(benefitsPlayer.length, playerTrades.length);
}