mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-14 04:19:22 +00:00
DEV: Add a script for dumping and comparing all asset layer pose mappings
This commit is contained in:
parent
d21c199975
commit
b973cb1ce3
9 changed files with 14532 additions and 148 deletions
.gitattributes
BondageClub
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,3 +1,4 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
BondageClub/Tools/Node/package-lock.json linguist-generated -diff
|
||||
BondageClub/Tools/Node/PoseMappingJSON/* linguist-generated
|
||||
|
|
|
@ -1,148 +1,10 @@
|
|||
"use strict";
|
||||
|
||||
const vm = require("vm");
|
||||
const fs = require("fs");
|
||||
|
||||
const BASE_PATH = "../../";
|
||||
// Files needed to check the Female3DCG assets
|
||||
const NEEDED_FILES = [
|
||||
"Scripts/Common.js",
|
||||
"Scripts/Dialog.js",
|
||||
"Scripts/Asset.js",
|
||||
"Scripts/ExtendedItem.js",
|
||||
"Scripts/ModularItem.js",
|
||||
"Scripts/TypedItem.js",
|
||||
"Scripts/VariableHeight.js",
|
||||
"Scripts/VibratorMode.js",
|
||||
"Scripts/Property.js",
|
||||
"Scripts/TextItem.js",
|
||||
"Screens/Inventory/Futuristic/Futuristic.js",
|
||||
"Screens/Inventory/ItemTorso/FuturisticHarness/FuturisticHarness.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarNameTag/CollarNameTag.js",
|
||||
"Screens/Inventory/ItemArms/FullLatexSuit/FullLatexSuit.js",
|
||||
"Screens/Inventory/ItemButt/InflVibeButtPlug/InflVibeButtPlug.js",
|
||||
"Screens/Inventory/ItemDevices/VacBedDeluxe/VacBedDeluxe.js",
|
||||
"Screens/Inventory/ItemDevices/WoodenBox/WoodenBox.js",
|
||||
"Screens/Inventory/ItemPelvis/SciFiPleasurePanties/SciFiPleasurePanties.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarShockUnit/CollarShockUnit.js",
|
||||
"Screens/Inventory/ItemVulva/ClitAndDildoVibratorbelt/ClitAndDildoVibratorbelt.js",
|
||||
"Screens/Inventory/ItemBreast/FuturisticBra/FuturisticBra.js",
|
||||
"Screens/Inventory/ItemArms/TransportJacket/TransportJacket.js",
|
||||
"Screens/Inventory/ItemMouth/FuturisticPanelGag/FuturisticPanelGag.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarAutoShockUnit/CollarAutoShockUnit.js",
|
||||
"Screens/Inventory/ItemArms/PrisonLockdownSuit/PrisonLockdownSuit.js",
|
||||
"Screens/Inventory/ItemPelvis/LoveChastityBelt/LoveChastityBelt.js",
|
||||
"Screens/Inventory/ItemVulva/LoversVibrator/LoversVibrator.js",
|
||||
"Screens/Inventory/ItemButt/AnalBeads2/AnalBeads2.js",
|
||||
"Screens/Inventory/ItemDevices/LuckyWheel/LuckyWheel.js",
|
||||
"Screens/Inventory/ItemDevices/FuturisticCrate/FuturisticCrate.js",
|
||||
"Screens/Inventory/Cloth/CheerleaderTop/CheerleaderTop.js",
|
||||
"Screens/Inventory/ClothAccessory/Bib/Bib.js",
|
||||
"Screens/Inventory/ItemDevices/DollBox/DollBox.js",
|
||||
"Screens/Inventory/ItemDevices/PetBowl/PetBowl.js",
|
||||
"Screens/Inventory/ItemHead/DroneMask/DroneMask.js",
|
||||
"Screens/Inventory/ItemMisc/WoodenSign/WoodenSign.js",
|
||||
"Screens/Inventory/ItemHood/CanvasHood/CanvasHood.js",
|
||||
"Screens/Inventory/ItemPelvis/ObedienceBelt/ObedienceBelt.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CustomCollarTag/CustomCollarTag.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/ElectronicTag/ElectronicTag.js",
|
||||
"Screens/Inventory/ItemNeckRestraints/PetPost/PetPost.js",
|
||||
"Screens/Inventory/ItemVulva/FuturisticVibrator/FuturisticVibrator.js",
|
||||
"Screens/Inventory/ItemPelvis/FuturisticTrainingBelt/FuturisticTrainingBelt.js",
|
||||
"Screens/Inventory/ItemDevices/KabeshiriWall/KabeshiriWall.js",
|
||||
"Screens/Inventory/ItemDevices/FuckMachine/FuckMachine.js",
|
||||
"Screens/Inventory/ItemBreast/ForbiddenChastityBra/ForbiddenChastityBra.js",
|
||||
"Screens/Inventory/Suit/LatexCatsuit/LatexCatsuit.js",
|
||||
"Assets/Female3DCG/Female3DCG.js",
|
||||
"Assets/Female3DCG/Female3DCGExtended.js",
|
||||
"Scripts/Translation.js",
|
||||
"Scripts/Text.js",
|
||||
"Screens/Character/ItemColor/ItemColor.js",
|
||||
"Scripts/Testing.js",
|
||||
];
|
||||
|
||||
let localError = false;
|
||||
let globalError = false;
|
||||
/**
|
||||
* Logs the error to console and sets erroneous exit code
|
||||
* @param {string} text The error
|
||||
*/
|
||||
function error(text) {
|
||||
console.log("ERROR:", text);
|
||||
process.exitCode = 1;
|
||||
localError = true;
|
||||
globalError = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSV file content into an array
|
||||
* @param {string} str - Content of the CSV
|
||||
* @returns {string[][]} Array representing each line of the parsed content, each line itself is split by commands and stored within an array.
|
||||
*/
|
||||
function CommonParseCSV(str) {
|
||||
/** @type {string[][]} */
|
||||
let arr = [];
|
||||
let quote = false; // true means we're inside a quoted field
|
||||
let c;
|
||||
let col;
|
||||
// We remove whitespace on start and end
|
||||
str = str.trim() + "\n";
|
||||
|
||||
// iterate over each character, keep track of current row and column (of the returned array)
|
||||
for (let row = (col = c = 0); c < str.length; c++) {
|
||||
var cc = str[c],
|
||||
nc = str[c + 1]; // current character, next character
|
||||
arr[row] = arr[row] || []; // create a new row if necessary
|
||||
arr[row][col] = arr[row][col] || ""; // create a new column (start with empty string) if necessary
|
||||
|
||||
// If the current character is a quotation mark, and we're inside a
|
||||
// quoted field, and the next character is also a quotation mark,
|
||||
// add a quotation mark to the current column and skip the next character
|
||||
if (cc == '"' && quote && nc == '"') {
|
||||
arr[row][col] += cc;
|
||||
++c;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's just one quotation mark, begin/end quoted field
|
||||
if (cc == '"') {
|
||||
quote = !quote;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a comma and we're not in a quoted field, move on to the next column
|
||||
if (cc == "," && !quote) {
|
||||
++col;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a newline and we're not in a quoted field, move on to the next
|
||||
// row and move to column 0 of that new row
|
||||
if (cc == "\n" && !quote) {
|
||||
++row;
|
||||
col = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, append the current character to the current column
|
||||
arr[row][col] += cc;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a CSV file and verifies correct column widths
|
||||
* @param {string} path Path to file, relative to BondageClub directory
|
||||
* @param {number} expectedWidth Expected number of columns
|
||||
*/
|
||||
function loadCSV(path, expectedWidth) {
|
||||
const data = CommonParseCSV(fs.readFileSync(BASE_PATH + path, { encoding: "utf-8" }));
|
||||
for (let line = 0; line < data.length; line++) {
|
||||
if (data[line].length !== expectedWidth) {
|
||||
error(`Bad width of line ${line + 1} (${data[line].length} vs ${expectedWidth}) in ${path}`);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
const { NEEDED_FILES, BASE_PATH, error, loadCSV } = require("./Common.js");
|
||||
const common = require("./Common.js");
|
||||
|
||||
/**
|
||||
* Checks for {@link AssetDefinition.DynamicGroupName}
|
||||
|
@ -1258,7 +1120,7 @@ function sanitizeVMOutput(input) {
|
|||
const dialogArray = loadCSV("Screens/Character/Player/Dialog_Player.csv", 6);
|
||||
|
||||
// No further checks if initial data load failed
|
||||
if (localError) {
|
||||
if (common.localError) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1270,7 +1132,7 @@ function sanitizeVMOutput(input) {
|
|||
|
||||
// Check all groups
|
||||
for (const Group of AssetFemale3DCG) {
|
||||
localError = false;
|
||||
common.localError = false;
|
||||
|
||||
Groups.push(Group);
|
||||
/** @type {AssetDefinition[]} */
|
||||
|
@ -1284,7 +1146,7 @@ function sanitizeVMOutput(input) {
|
|||
});
|
||||
continue;
|
||||
}
|
||||
localError = false;
|
||||
common.localError = false;
|
||||
|
||||
// Check any extended item config
|
||||
if (Asset.Extended) {
|
||||
|
@ -1319,13 +1181,13 @@ function sanitizeVMOutput(input) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!localError) {
|
||||
if (!common.localError) {
|
||||
GroupAssets.push(Asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (globalError) {
|
||||
if (common.globalError) {
|
||||
console.log("WARNING: Type errors detected, skipping other checks");
|
||||
return;
|
||||
}
|
||||
|
|
180
BondageClub/Tools/Node/Common.js
Normal file
180
BondageClub/Tools/Node/Common.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/** Common utility scripts for the test suit. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
/** Files needed to check the Female3DCG assets. */
|
||||
const NEEDED_FILES = [
|
||||
"Scripts/Common.js",
|
||||
"Scripts/Dialog.js",
|
||||
"Scripts/Asset.js",
|
||||
"Scripts/ExtendedItem.js",
|
||||
"Scripts/ModularItem.js",
|
||||
"Scripts/TypedItem.js",
|
||||
"Scripts/VariableHeight.js",
|
||||
"Scripts/VibratorMode.js",
|
||||
"Scripts/Property.js",
|
||||
"Scripts/TextItem.js",
|
||||
"Screens/Inventory/Futuristic/Futuristic.js",
|
||||
"Screens/Inventory/ItemTorso/FuturisticHarness/FuturisticHarness.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarNameTag/CollarNameTag.js",
|
||||
"Screens/Inventory/ItemArms/FullLatexSuit/FullLatexSuit.js",
|
||||
"Screens/Inventory/ItemButt/InflVibeButtPlug/InflVibeButtPlug.js",
|
||||
"Screens/Inventory/ItemDevices/VacBedDeluxe/VacBedDeluxe.js",
|
||||
"Screens/Inventory/ItemDevices/WoodenBox/WoodenBox.js",
|
||||
"Screens/Inventory/ItemPelvis/SciFiPleasurePanties/SciFiPleasurePanties.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarShockUnit/CollarShockUnit.js",
|
||||
"Screens/Inventory/ItemVulva/ClitAndDildoVibratorbelt/ClitAndDildoVibratorbelt.js",
|
||||
"Screens/Inventory/ItemBreast/FuturisticBra/FuturisticBra.js",
|
||||
"Screens/Inventory/ItemArms/TransportJacket/TransportJacket.js",
|
||||
"Screens/Inventory/ItemMouth/FuturisticPanelGag/FuturisticPanelGag.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CollarAutoShockUnit/CollarAutoShockUnit.js",
|
||||
"Screens/Inventory/ItemArms/PrisonLockdownSuit/PrisonLockdownSuit.js",
|
||||
"Screens/Inventory/ItemPelvis/LoveChastityBelt/LoveChastityBelt.js",
|
||||
"Screens/Inventory/ItemVulva/LoversVibrator/LoversVibrator.js",
|
||||
"Screens/Inventory/ItemButt/AnalBeads2/AnalBeads2.js",
|
||||
"Screens/Inventory/ItemDevices/LuckyWheel/LuckyWheel.js",
|
||||
"Screens/Inventory/ItemDevices/FuturisticCrate/FuturisticCrate.js",
|
||||
"Screens/Inventory/Cloth/CheerleaderTop/CheerleaderTop.js",
|
||||
"Screens/Inventory/ClothAccessory/Bib/Bib.js",
|
||||
"Screens/Inventory/ItemDevices/DollBox/DollBox.js",
|
||||
"Screens/Inventory/ItemDevices/PetBowl/PetBowl.js",
|
||||
"Screens/Inventory/ItemHead/DroneMask/DroneMask.js",
|
||||
"Screens/Inventory/ItemMisc/WoodenSign/WoodenSign.js",
|
||||
"Screens/Inventory/ItemHood/CanvasHood/CanvasHood.js",
|
||||
"Screens/Inventory/ItemPelvis/ObedienceBelt/ObedienceBelt.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/CustomCollarTag/CustomCollarTag.js",
|
||||
"Screens/Inventory/ItemNeckAccessories/ElectronicTag/ElectronicTag.js",
|
||||
"Screens/Inventory/ItemNeckRestraints/PetPost/PetPost.js",
|
||||
"Screens/Inventory/ItemVulva/FuturisticVibrator/FuturisticVibrator.js",
|
||||
"Screens/Inventory/ItemPelvis/FuturisticTrainingBelt/FuturisticTrainingBelt.js",
|
||||
"Screens/Inventory/ItemDevices/KabeshiriWall/KabeshiriWall.js",
|
||||
"Screens/Inventory/ItemDevices/FuckMachine/FuckMachine.js",
|
||||
"Screens/Inventory/ItemBreast/ForbiddenChastityBra/ForbiddenChastityBra.js",
|
||||
"Screens/Inventory/Suit/LatexCatsuit/LatexCatsuit.js",
|
||||
"Assets/Female3DCG/Female3DCG.js",
|
||||
"Assets/Female3DCG/Female3DCGExtended.js",
|
||||
"Scripts/Translation.js",
|
||||
"Scripts/Text.js",
|
||||
"Screens/Character/ItemColor/ItemColor.js",
|
||||
"Scripts/Testing.js",
|
||||
];
|
||||
|
||||
/** The base path for any BC asset/script lookup. */
|
||||
const BASE_PATH = "../../";
|
||||
|
||||
let localError = false;
|
||||
let globalError = false;
|
||||
|
||||
/**
|
||||
* Logs the error to console and sets erroneous exit code
|
||||
* @param {string} text The error
|
||||
*/
|
||||
function error(text) {
|
||||
console.log("ERROR:", text);
|
||||
process.exitCode = 1;
|
||||
localError = true;
|
||||
globalError = true;
|
||||
}
|
||||
|
||||
/** @see {@link Object.entries} */
|
||||
const entries = /** @type {<KT extends String, VT>(record: Partial<Record<KT, VT>>) => [key: KT, value: VT][]} */(Object.entries);
|
||||
|
||||
/** @see {@link Object.keys} */
|
||||
const keys = /** @type {<KT extends String>(record: Partial<Record<KT, unknown>>) => KT[]} */(Object.keys);
|
||||
|
||||
/** @see {@link Object.fromEntries} */
|
||||
const fromEntries = /** @type {<KT extends String, VT>(list: [key: KT, value: VT][]) => Record<KT, VT>} */(Object.fromEntries);
|
||||
|
||||
/**
|
||||
* Return whether the passed object is a record/interface.
|
||||
* @type {(obj: unknown) => obj is Record<string, unknown>}
|
||||
*/
|
||||
function isObject(obj) {
|
||||
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSV file content into an array
|
||||
* @param {string} str - Content of the CSV
|
||||
* @returns {string[][]} Array representing each line of the parsed content, each line itself is split by commands and stored within an array.
|
||||
*/
|
||||
function parseCSV(str) {
|
||||
/** @type {string[][]} */
|
||||
let arr = [];
|
||||
let quote = false; // true means we're inside a quoted field
|
||||
let c;
|
||||
let col;
|
||||
// We remove whitespace on start and end
|
||||
str = str.trim() + "\n";
|
||||
|
||||
// iterate over each character, keep track of current row and column (of the returned array)
|
||||
for (let row = (col = c = 0); c < str.length; c++) {
|
||||
var cc = str[c],
|
||||
nc = str[c + 1]; // current character, next character
|
||||
arr[row] = arr[row] || []; // create a new row if necessary
|
||||
arr[row][col] = arr[row][col] || ""; // create a new column (start with empty string) if necessary
|
||||
|
||||
// If the current character is a quotation mark, and we're inside a
|
||||
// quoted field, and the next character is also a quotation mark,
|
||||
// add a quotation mark to the current column and skip the next character
|
||||
if (cc == '"' && quote && nc == '"') {
|
||||
arr[row][col] += cc;
|
||||
++c;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's just one quotation mark, begin/end quoted field
|
||||
if (cc == '"') {
|
||||
quote = !quote;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a comma and we're not in a quoted field, move on to the next column
|
||||
if (cc == "," && !quote) {
|
||||
++col;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a newline and we're not in a quoted field, move on to the next
|
||||
// row and move to column 0 of that new row
|
||||
if (cc == "\n" && !quote) {
|
||||
++row;
|
||||
col = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, append the current character to the current column
|
||||
arr[row][col] += cc;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a CSV file and verifies correct column widths
|
||||
* @param {string} path Path to file, relative to BondageClub directory
|
||||
* @param {number} expectedWidth Expected number of columns
|
||||
*/
|
||||
function loadCSV(path, expectedWidth) {
|
||||
const data = parseCSV(fs.readFileSync(BASE_PATH + path, { encoding: "utf-8" }));
|
||||
for (let line = 0; line < data.length; line++) {
|
||||
if (data[line].length !== expectedWidth) {
|
||||
error(`Bad width of line ${line + 1} (${data[line].length} vs ${expectedWidth}) in ${path}`);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NEEDED_FILES,
|
||||
BASE_PATH,
|
||||
localError,
|
||||
globalError,
|
||||
error,
|
||||
entries,
|
||||
keys,
|
||||
fromEntries,
|
||||
isObject,
|
||||
loadCSV,
|
||||
};
|
227
BondageClub/Tools/Node/PoseMapping.js
Normal file
227
BondageClub/Tools/Node/PoseMapping.js
Normal file
|
@ -0,0 +1,227 @@
|
|||
"use strict";
|
||||
|
||||
const vm = require("vm");
|
||||
const fs = require("fs");
|
||||
const process = require("process");
|
||||
const minimist = require("minimist");
|
||||
const util = require('util');
|
||||
|
||||
const { NEEDED_FILES, BASE_PATH, loadCSV, entries, fromEntries, keys, isObject } = require("./Common.js");
|
||||
|
||||
const HELP = `\
|
||||
Script for dumping and comparing pose mappings.
|
||||
|
||||
Usage:
|
||||
node PoseMapping.js [options]
|
||||
|
||||
Options:
|
||||
-h, --help Show help
|
||||
--dump <path> Write the current pose mappings to the specified JSON file
|
||||
--compare <path> Compare the current pose mappings with those in the passed JSON file
|
||||
`;
|
||||
|
||||
/** @typedef {string} AssetName */
|
||||
/** @typedef {string} LayerName */
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Record<AssetGroupName, Record<AssetName, Record<LayerName, T>>>} LayerMapping
|
||||
*/
|
||||
/** @typedef {LayerMapping<AssetPoseMapping>} AllPoseMappings */
|
||||
/** @typedef {Partial<Record<AssetPoseName, Record<"-" | "+", AssetPoseName | PoseType>>>} DiffRecord */
|
||||
/** @typedef {[key: AssetPoseName, value: Record<"-" | "+", AssetPoseName | PoseType>][]} DiffList */
|
||||
/** @typedef {Partial<LayerMapping<DiffRecord>>} AllDiffMappings */
|
||||
|
||||
/**
|
||||
* @param {minimist.ParsedArgs} argv
|
||||
* @returns {{ help?: boolean, dump?: string, compare?: string }}
|
||||
*/
|
||||
function validateArgv(argv) {
|
||||
const { _, ...kwargs } = argv;
|
||||
delete kwargs.h;
|
||||
|
||||
/** @type {string[]} */
|
||||
const invalidArguments = [];
|
||||
invalidArguments.push(..._);
|
||||
|
||||
/** @type {ReturnType<typeof validateArgv>} */
|
||||
const ret = {};
|
||||
const validKeys = new Set(["help", "dump", "compare"]);
|
||||
for (const [k, v] of Object.entries(kwargs)) {
|
||||
if (validKeys.has(k)) {
|
||||
ret[k] = v;
|
||||
} else {
|
||||
invalidArguments.push(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidArguments.length > 0) {
|
||||
throw new Error(`Found ${invalidArguments.length} unknown arguments: ${invalidArguments.join()}`);
|
||||
} else if (keys(ret).length === 0) {
|
||||
throw new Error(`Expects at least one argument`);
|
||||
} else if ("dump" in kwargs && !kwargs.dump) {
|
||||
throw new Error("Dump file path must be a non-empty string");
|
||||
} else if ("compare" in kwargs && !kwargs.compare) {
|
||||
throw new Error("Compare file path must be a non-empty string");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the pose mappings, extracted from the passed assets, to the output json file specified in `outputPath`
|
||||
* @param {readonly Asset[]} assets
|
||||
* @returns {AllPoseMappings}
|
||||
*/
|
||||
function gatherPoseMappings(assets) {
|
||||
const layers = [...assets].sort((a, b) => {
|
||||
const groupPrio = a.Group.Name.localeCompare(b.Group.Name);
|
||||
const assetPrio = a.Name.localeCompare(b.Name);
|
||||
return groupPrio || assetPrio;
|
||||
}).flatMap(a => a.Layer);
|
||||
|
||||
const ret = /** @type {AllPoseMappings} */({});
|
||||
for (const layer of layers) {
|
||||
const layerName = layer.Name ?? "";
|
||||
const assetName = layer.Asset.Name;
|
||||
const groupName = layer.Asset.Group.Name;
|
||||
|
||||
if (!ret[groupName]) {
|
||||
ret[groupName] = {};
|
||||
}
|
||||
if (!ret[groupName][assetName]) {
|
||||
ret[groupName][assetName] = {};
|
||||
}
|
||||
|
||||
const posesSorted = fromEntries(entries(layer.PoseMapping).sort((i, j) => i[0].localeCompare(j[0])).filter(i => i[1]));
|
||||
ret[groupName][assetName][layerName] = posesSorted;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the difference between the pose mappings in `newData` and `oldData`.
|
||||
* Assets and groups absent in `oldData` are ignored.
|
||||
* @param {AllPoseMappings} newData
|
||||
* @param {Partial<AllPoseMappings>} oldData
|
||||
* @param {readonly Pose[]} poses
|
||||
* @returns {AllDiffMappings | null}
|
||||
*/
|
||||
function getPoseMappingsDiff(newData, oldData, poses) {
|
||||
const poseNames = poses.map(p => p.Name).sort();
|
||||
|
||||
/** @type {AllDiffMappings} */
|
||||
const diffMapping = {};
|
||||
for (const [groupName, assetNew] of entries(newData)) {
|
||||
const assetOld = oldData[groupName];
|
||||
if (!isObject(assetOld)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [assetName, layerNew] of entries(assetNew)) {
|
||||
const layerOld = assetOld[assetName];
|
||||
if (!isObject(layerOld)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [layerName, posesNew] of entries(layerNew)) {
|
||||
const posesOld = layerOld[layerName];
|
||||
if (!isObject(posesOld)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const diff = /** @type {DiffList} */(poseNames.map(k => {
|
||||
const poseNew = posesNew[k] ?? "";
|
||||
const poseOld = posesOld[k] ?? "";
|
||||
if (poseNew === poseOld) {
|
||||
return null;
|
||||
} else {
|
||||
return [k, { "+": poseNew, "-": poseOld }];
|
||||
}
|
||||
}).filter(i => i !== null));
|
||||
|
||||
if (diff.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let groupDiff = diffMapping[groupName];
|
||||
if (!groupDiff) groupDiff = diffMapping[groupName] = {};
|
||||
let assetDiff = groupDiff[assetName];
|
||||
if (!assetDiff) assetDiff = groupDiff[assetName] = {};
|
||||
assetDiff[layerName] = fromEntries(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys(diffMapping).length === 0 ? null : diffMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{ poseMapping: AllPoseMappings, poses: Pose[] }}
|
||||
*/
|
||||
function runVM() {
|
||||
const [commonFile, ...neededFiles] = NEEDED_FILES;
|
||||
/** @type {vm.Context & { Asset?: Asset[], Pose?: Pose[] }} */
|
||||
const context = vm.createContext({
|
||||
OuterArray: Array,
|
||||
Object: Object,
|
||||
TestingColorLayers: new Set(loadCSV("Assets/Female3DCG/LayerNames.csv", 2).map(i => i[0])),
|
||||
TestingColorGroups: new Set(loadCSV("Assets/Female3DCG/ColorGroups.csv", 2).map(i => i[0])),
|
||||
});
|
||||
vm.runInContext(fs.readFileSync(BASE_PATH + commonFile, { encoding: "utf-8" }), context, {
|
||||
filename: commonFile,
|
||||
});
|
||||
|
||||
// Only patch `CommonGet` after loading `Common`, lest our monkey patch will be overriden again
|
||||
context.CommonGet = (file, callback) => {
|
||||
const data = fs.readFileSync(`../../${file}`, "utf8");
|
||||
const obj = {
|
||||
status: 200,
|
||||
responseText: data,
|
||||
};
|
||||
callback.bind(obj)(obj);
|
||||
};
|
||||
for (const file of neededFiles) {
|
||||
vm.runInContext(fs.readFileSync(BASE_PATH + file, { encoding: "utf-8" }), context, {
|
||||
filename: file,
|
||||
});
|
||||
}
|
||||
|
||||
const { Asset, Pose } = context;
|
||||
if (!Asset || !Pose) {
|
||||
throw new Error("Failed to generate the `Asset` and/or `Pose` arrays");
|
||||
}
|
||||
|
||||
return { poseMapping: gatherPoseMappings(Asset), poses: Pose };
|
||||
}
|
||||
|
||||
(function () {
|
||||
const kwargs = validateArgv(minimist(
|
||||
process.argv.slice(2),
|
||||
{ string: ["compare", "dump"], alias: { "h": "help" } },
|
||||
));
|
||||
if (kwargs.help) {
|
||||
console.log(HELP);
|
||||
return;
|
||||
}
|
||||
|
||||
const { poseMapping, poses } = runVM();
|
||||
|
||||
if (kwargs.dump !== undefined) {
|
||||
fs.writeFileSync(kwargs.dump, JSON.stringify(poseMapping, undefined, 4));
|
||||
console.log(`Succesfully written pose mapping to "${kwargs.dump}"`);
|
||||
}
|
||||
|
||||
if (kwargs.compare !== undefined) {
|
||||
/** @type {null | Record<string, any> | any[]} */
|
||||
const poseMappingOld = JSON.parse(fs.readFileSync(kwargs.compare, { encoding: "utf8" }));
|
||||
if (!isObject(poseMappingOld)) {
|
||||
throw new Error(`Invalid "${kwargs.compare}" JSON data expected a nested record`);
|
||||
}
|
||||
|
||||
const diff = getPoseMappingsDiff(poseMapping, poseMappingOld, poses);
|
||||
if (diff !== null) {
|
||||
console.error(util.inspect(diff, { depth: null, colors: true }));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`Succesfully compared "${kwargs.compare}" pose mappings without conflict`);
|
||||
}
|
||||
}
|
||||
})();
|
14097
BondageClub/Tools/Node/PoseMappingJSON/R97Beta4.1.json
generated
Normal file
14097
BondageClub/Tools/Node/PoseMappingJSON/R97Beta4.1.json
generated
Normal file
File diff suppressed because it is too large
Load diff
13
BondageClub/Tools/Node/README.md
Normal file
13
BondageClub/Tools/Node/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
Various utility scripts used for running the CI and other dev-related tasks.
|
||||
|
||||
AssetCheck.js
|
||||
-------------
|
||||
Tests for various asset-related properties.
|
||||
|
||||
PoseMapping.js
|
||||
--------------
|
||||
Scripts for dumping and comparing asset layer pose mappings. JSON dumps from older BC versions can be stored in `PoseMappingJSON/`.
|
||||
|
||||
GenerateChangelog.js
|
||||
--------------------
|
||||
Helper script for generating the BC changelog.
|
|
@ -10,6 +10,7 @@
|
|||
"include": [
|
||||
"../../Assets/Female3DCG/Female3DCG_Types.d.ts",
|
||||
"../../Scripts/Typedef.d.ts",
|
||||
"AssetCheck.js"
|
||||
"AssetCheck.js",
|
||||
"PoseMapping.js"
|
||||
]
|
||||
}
|
||||
|
|
1
BondageClub/package-lock.json
generated
1
BondageClub/package-lock.json
generated
|
@ -32,6 +32,7 @@
|
|||
"gulp-size": "^4.0.1",
|
||||
"imagemin-jpegtran": "^7.0.0",
|
||||
"marked": "^4.0.10",
|
||||
"minimist": "^1.2.6",
|
||||
"node-fetch": "^2.6.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.0.3",
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"assets:lint:fix": "prettier-eslint --write Assets/Female3DCG/Female3DCG.js Assets/Female3DCG/Female3DCGExtended.js",
|
||||
"assets:check": "cd Tools/Node && node --unhandled-rejections=strict AssetCheck",
|
||||
"assets:typecheck": "cd ../ && tsc -p BondageClub/Tools/Node/tsconfig-assetcheck.json",
|
||||
"assets:posemapping": "cd Tools/Node && node --unhandled-rejections=strict PoseMapping",
|
||||
"scripts:lint": "eslint \"Scripts/**/*.js\" \"Screens/**/*.js\" \"Tools/**/*.js\" \"Backgrounds/Backgrounds.js\"",
|
||||
"scripts:lint:fix": "eslint --fix \"Scripts/**/*.js\" \"Screens/**/*.js\" \"Tools/**/*.js\" \"Backgrounds/Backgrounds.js\"",
|
||||
"scripts:typecheck": "cd ../ && tsc -p BondageClub/jsconfig.json",
|
||||
|
@ -61,7 +62,8 @@
|
|||
"through2": "^4.0.2",
|
||||
"typescript": "^5.2.2",
|
||||
"@types/css-font-loading-module": "^0.0.9",
|
||||
"@types/node": "^20.8.7"
|
||||
"@types/node": "^20.8.7",
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"pixi-filters": "^5.2.1",
|
||||
|
|
Loading…
Add table
Reference in a new issue