Merge branch 'feature/figure-data-converter' into 'master'

added figuredata converter

See merge request nitro/nitro-converter!6
This commit is contained in:
Bill 2021-08-03 18:17:56 +00:00
commit d8e1cc0c1a
27 changed files with 794 additions and 2 deletions

View File

@ -6,6 +6,7 @@
"typescript.format.placeOpenBraceOnNewLineForFunctions": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.sortJSON": false,
"source.organizeImports": true,
},
"emmet.showExpandedAbbreviation": "never",

View File

@ -5,6 +5,7 @@
The converter currently supports the following files:
- furnidata.xml
- figuredata.xml
- figuremap.xml
- effectmap.xml
- external_texts.txt
@ -40,6 +41,7 @@ You may set any of the urls to a local path on your system or a remote url. A lo
| convert.productdata | Either `0` to skip or `1` to run |
| convert.externaltexts | Either `0` to skip or `1` to run |
| convert.figure | Either `0` to skip or `1` to run |
| convert.figuredata | Either `0` to skip or `1` to run |
| convert.effect | Either `0` to skip or `1` to run |
| convert.furniture | Either `0` to skip or `1` to run |
| convert.pet | Either `0` to skip or `1` to run |
@ -50,6 +52,6 @@ To run the converter open a new terminal / console window in the main converter
**Make sure you run ``npm i`` before first use.**
Type `npm run start:dev` and the converter will start running, only errors will be outputted in the console.
Type `npm start` and the converter will start running, only errors will be outputted in the console.
The converter will skip any assets that already exist but will always reconvert your XMLs / copy your JSONS to the ``gamedata`` folder to ensure you always have the latest copy.

View File

@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node-transpile-only src/Main.ts",
"start:dev": "ts-node-dev --respawn --transpile-only src/Main.ts"
},
"author": "",

View File

@ -5,6 +5,7 @@ import { IConverter } from './common/converters/IConverter';
import { EffectConverter } from './converters/effect/EffectConverter';
import { ExternalTextsConverter } from './converters/externaltexts/ExternalTextsConverter';
import { FigureConverter } from './converters/figure/FigureConverter';
import { FigureDataConverter } from './converters/figuredata/FigureDataConverter';
import { FurnitureConverter } from './converters/furniture/FurnitureConverter';
import { PetConverter } from './converters/pet/PetConverter';
import { ProductDataConverter } from './converters/productdata/ProductDataConverter';
@ -12,6 +13,7 @@ import { ProductDataConverter } from './converters/productdata/ProductDataConver
(async () =>
{
checkNodeVersion();
const config = container.resolve(Configuration);
await config.init();
@ -21,7 +23,8 @@ import { ProductDataConverter } from './converters/productdata/ProductDataConver
FigureConverter,
EffectConverter,
FurnitureConverter,
PetConverter
PetConverter,
FigureDataConverter
];
for(const converterClass of converters)

View File

@ -3,6 +3,7 @@
"flash.client.url": "",
"furnidata.load.url": "",
"productdata.load.url": "",
"figuredata.load.url": "https://www.habbo.com/gamedata/figuredata/1",
"figuremap.load.url": "${flash.client.url}figuremap.xml",
"effectmap.load.url": "${flash.client.url}effectmap.xml",
"dynamic.download.pet.url": "${flash.client.url}%className%.swf",
@ -15,6 +16,7 @@
"convert.productdata": "1",
"convert.externaltexts": "1",
"convert.figure": "1",
"convert.figuredata": "1",
"convert.effect": "1",
"convert.furniture": "1",
"convert.pet": "1"

View File

@ -0,0 +1,103 @@
import { writeFile } from 'fs/promises';
import * as ora from 'ora';
import { singleton } from 'tsyringe';
import { parseStringPromise } from 'xml2js';
import { Configuration } from '../../common/config/Configuration';
import { Converter } from '../../common/converters/Converter';
import { FigureDataMapper } from '../../mapping/mappers/FigureDataMapper';
import File from '../../utils/File';
import { Logger } from '../../utils/Logger';
import { IFigureData } from './../../mapping/json/figuredata/IFigureData';
import { FigureDataDownloader } from './FigureDataDownloader';
@singleton()
export class FigureDataConverter extends Converter
{
constructor(
private readonly _figureDataDownloader: FigureDataDownloader,
private readonly _configuration: Configuration,
private readonly _logger: Logger)
{
super();
}
public async convertAsync(): Promise<void>
{
if(!this._configuration.getBoolean('convert.figuredata')) return;
return new Promise((resolve, reject) =>
{
const now = Date.now();
const spinner = ora('Preparing FigureData').start();
const directory = this.getDirectory();
try
{
this._figureDataDownloader.download(async (content: string) =>
{
spinner.text = 'Parsing FigureData';
spinner.render();
let figureDataString = content;
if(!figureDataString.startsWith('{'))
{
const xml = await parseStringPromise(figureDataString);
const figureData = await this.mapXML2JSON(xml);
figureDataString = JSON.stringify(figureData);
}
const path = directory.path + '/FigureData.json';
await writeFile(path, figureDataString, 'utf8');
this._configuration.setValue('figuredata.load.url', path);
spinner.succeed(`FigureData finished in ${ Date.now() - now }ms`);
resolve();
});
}
catch (error)
{
spinner.fail('FigureData failed: ' + error.message);
reject(error);
}
});
}
private getDirectory(): File
{
const baseFolder = new File(this._configuration.getValue('output.folder'));
if(!baseFolder.isDirectory()) baseFolder.mkdirs();
const gameDataFolder = new File(baseFolder.path + '/gamedata');
if(!gameDataFolder.isDirectory()) gameDataFolder.mkdirs();
const jsonFolder = new File(gameDataFolder.path + '/json');
if(!jsonFolder.isDirectory()) jsonFolder.mkdirs();
return jsonFolder;
}
private async mapXML2JSON(xml: any): Promise<IFigureData>
{
if(!xml) return null;
const output: IFigureData = {};
FigureDataMapper.mapXML(xml, output);
return output;
}
}

View File

@ -0,0 +1,32 @@
import { singleton } from 'tsyringe';
import { Configuration } from '../../common/config/Configuration';
import { FileUtilities } from '../../utils/FileUtilities';
@singleton()
export class FigureDataDownloader
{
constructor(private readonly _configuration: Configuration)
{}
public async download(callback: (content: string) => Promise<void>): Promise<void>
{
const figureData = await this.parseFigureData();
if(!figureData) throw new Error('invalid_figure_data');
callback(figureData);
}
public async parseFigureData(): Promise<string>
{
const url = this._configuration.getValue('figuredata.load.url');
if(!url || !url.length) return null;
const content = await FileUtilities.readFileAsString(url);
if(!content || !content.length) return null;
return content;
}
}

View File

@ -0,0 +1,8 @@
import { IFigureDataPalette } from './IFigureDataPalette';
import { IFigureDataSetType } from './IFigureDataSetType';
export interface IFigureData
{
palettes?: IFigureDataPalette[];
setTypes?: IFigureDataSetType[];
}

View File

@ -0,0 +1,8 @@
export interface IFigureDataColor
{
id?: number;
index?: number;
club?: number;
selectable?: boolean;
hexCode?: string;
}

View File

@ -0,0 +1,4 @@
export interface IFigureDataHiddenLayer
{
partType?: string;
}

View File

@ -0,0 +1,7 @@
import { IFigureDataColor } from './IFigureDataColor';
export interface IFigureDataPalette
{
id?: number;
colors?: IFigureDataColor[];
}

View File

@ -0,0 +1,8 @@
export interface IFigureDataPart
{
id?: number;
type?: string;
colorable?: boolean;
index?: number;
colorindex?: number;
}

View File

@ -0,0 +1,15 @@
import { IFigureDataHiddenLayer } from './IFigureDataHiddenLayer';
import { IFigureDataPart } from './IFigureDataPart';
export interface IFigureDataSet
{
id?: number;
gender?: string;
club?: number;
colorable?: boolean;
selectable?: boolean;
preselectable?: boolean;
sellable?: boolean;
parts?: IFigureDataPart[];
hiddenLayers?: IFigureDataHiddenLayer[];
}

View File

@ -0,0 +1,12 @@
import { IFigureDataSet } from './IFigureDataSet';
export interface IFigureDataSetType
{
type?: string;
paletteId?: number;
mandatory_m_0?: boolean;
mandatory_f_0?: boolean;
mandatory_m_1?: boolean;
mandatory_f_1?: boolean;
sets?: IFigureDataSet[];
}

View File

@ -0,0 +1,7 @@
export * from './IFigureData';
export * from './IFigureDataColor';
export * from './IFigureDataHiddenLayer';
export * from './IFigureDataPalette';
export * from './IFigureDataPart';
export * from './IFigureDataSet';
export * from './IFigureDataSetType';

View File

@ -1,6 +1,7 @@
export * from './asset';
export * from './effectmap';
export * from './externaltexts';
export * from './figuredata';
export * from './figuremap';
export * from './furnituredata';
export * from './productdata';

View File

@ -0,0 +1,193 @@
import { IFigureDataHiddenLayer } from '../json';
import { IFigureDataPalette } from '../json/figuredata/IFigureDataPalette';
import { IFigureDataSet } from '../json/figuredata/IFigureDataSet';
import { FigureDataHiddenLayerXML } from '../xml';
import { FigureDataColorXML } from '../xml/figuredata/FigureDataColorXML';
import { FigureDataPaletteXML } from '../xml/figuredata/FigureDataPaletteXML';
import { FigureDataXML } from '../xml/figuredata/FigureDataXML';
import { IFigureData } from './../json/figuredata/IFigureData';
import { IFigureDataColor } from './../json/figuredata/IFigureDataColor';
import { IFigureDataPart } from './../json/figuredata/IFigureDataPart';
import { IFigureDataSetType } from './../json/figuredata/IFigureDataSetType';
import { FigureDataPartXML } from './../xml/figuredata/FigureDataPartXML';
import { FigureDataSetTypeXML } from './../xml/figuredata/FigureDataSetTypeXML';
import { FigureDataSetXML } from './../xml/figuredata/FigureDataSetXML';
import { Mapper } from './asset/Mapper';
export class FigureDataMapper extends Mapper
{
public static mapXML(xml: any, output: IFigureData): void
{
if(!xml || !output) return;
if(xml.figuredata !== undefined) FigureDataMapper.mapFigureDataXML(new FigureDataXML(xml.figuredata), output);
}
private static mapFigureDataXML(xml: FigureDataXML, output: IFigureData): void
{
if(!xml || !output) return;
if(xml.colorPalettes !== undefined)
{
if(xml.colorPalettes.length)
{
output.palettes = [];
FigureDataMapper.mapFigureDataColorPalettesXML(xml.colorPalettes, output.palettes);
}
}
if(xml.sets !== undefined)
{
if(xml.sets.length)
{
output.setTypes = [];
FigureDataMapper.mapFigureDataSetTypes(xml.sets, output.setTypes);
}
}
}
private static mapFigureDataColorPalettesXML(xml: FigureDataPaletteXML[], output: IFigureDataPalette[]): void
{
if(!xml || !xml.length || !output) return;
for(const paletteXML of xml)
{
const palette: IFigureDataPalette = {};
if(paletteXML.id !== undefined) palette.id = paletteXML.id;
if(paletteXML.colors !== undefined)
{
if(paletteXML.colors.length)
{
palette.colors = [];
FigureDataMapper.mapFigureDataPaletteColorsXML(paletteXML.colors, palette.colors);
}
}
output.push(palette);
}
}
private static mapFigureDataPaletteColorsXML(xml: FigureDataColorXML[], output: IFigureDataColor[]): void
{
if(!xml || !xml.length || !output) return;
for(const colorXML of xml)
{
const color: IFigureDataColor = {};
if(colorXML.id !== undefined) color.id = colorXML.id;
if(colorXML.index !== undefined) color.index = colorXML.index;
if(colorXML.club !== undefined) color.club = colorXML.club;
if(colorXML.selectable !== undefined) color.selectable = colorXML.selectable;
if(colorXML.hexCode !== undefined) color.hexCode = colorXML.hexCode;
output.push(color);
}
}
private static mapFigureDataSetTypes(xml: FigureDataSetTypeXML[], output: IFigureDataSetType[]): void
{
if(!xml || !xml.length || !output) return;
for(const setTypeXML of xml)
{
const setType: IFigureDataSetType = {};
if(setTypeXML.type !== undefined) setType.type = setTypeXML.type;
if(setTypeXML.paletteId !== undefined) setType.paletteId = setTypeXML.paletteId;
if(setTypeXML.mandatoryF0 !== undefined) setType.mandatory_f_0 = setTypeXML.mandatoryF0;
if(setTypeXML.mandatoryF1 !== undefined) setType.mandatory_f_1 = setTypeXML.mandatoryF1;
if(setTypeXML.mandatoryM0 !== undefined) setType.mandatory_m_0 = setTypeXML.mandatoryM0;
if(setTypeXML.mandatoryM1 !== undefined) setType.mandatory_m_1 = setTypeXML.mandatoryM1;
if(setTypeXML.sets !== undefined)
{
if(setTypeXML.sets.length)
{
setType.sets = [];
FigureDataMapper.mapFigureDataSets(setTypeXML.sets, setType.sets);
}
}
output.push(setType);
}
}
private static mapFigureDataSets(xml: FigureDataSetXML[], output: IFigureDataSet[]): void
{
if(!xml || !xml.length || !output) return;
for(const setXML of xml)
{
const setType: IFigureDataSet = {};
if(setXML.id !== undefined) setType.id = setXML.id;
if(setXML.gender !== undefined) setType.gender = setXML.gender;
if(setXML.club !== undefined) setType.club = setXML.club;
if(setXML.colorable !== undefined) setType.colorable = setXML.colorable;
if(setXML.selectable !== undefined) setType.selectable = setXML.selectable;
if(setXML.preselectable !== undefined) setType.preselectable = setXML.preselectable;
if(setXML.sellable !== undefined) setType.sellable = setXML.sellable;
if(setXML.parts !== undefined)
{
if(setXML.parts.length)
{
setType.parts = [];
FigureDataMapper.mapFigureDataParts(setXML.parts, setType.parts);
}
}
if(setXML.hiddenLayers !== undefined)
{
if(setXML.hiddenLayers.length)
{
setType.hiddenLayers = [];
FigureDataMapper.mapFigureDataHiddenLayers(setXML.hiddenLayers, setType.hiddenLayers);
}
}
output.push(setType);
}
}
private static mapFigureDataParts(xml: FigureDataPartXML[], output: IFigureDataPart[]): void
{
if(!xml || !xml.length || !output) return;
for(const partXML of xml)
{
const part: IFigureDataPart = {};
if(partXML.id !== undefined) part.id = partXML.id;
if(partXML.type !== undefined) part.type = partXML.type;
if(partXML.colorable !== undefined) part.colorable = partXML.colorable;
if(partXML.index !== undefined) part.index = partXML.index;
if(partXML.colorIndex !== undefined) part.colorindex = partXML.colorIndex;
output.push(part);
}
}
private static mapFigureDataHiddenLayers(xml: FigureDataHiddenLayerXML[], output: IFigureDataHiddenLayer[]): void
{
if(!xml || !xml.length || !output) return;
for(const hiddenLayerXML of xml)
{
const hiddenLayer: IFigureDataHiddenLayer = {};
if(hiddenLayerXML.partType !== undefined) hiddenLayer.partType = hiddenLayerXML.partType;
output.push(hiddenLayer);
}
}
}

View File

@ -1,4 +1,5 @@
export * from './asset';
export * from './EffectMapMapper';
export * from './FigureDataMapper';
export * from './FigureMapMapper';
export * from './FurnitureDataMapper';

View File

@ -0,0 +1,45 @@
export class FigureDataColorXML
{
private _id: number;
private _index: number;
private _club: number;
private _selectable: boolean;
private _hexCode: string;
constructor(xml: any)
{
const attributes = xml.$;
this._id = ((attributes && parseInt(attributes.id)) || 0);
this._index = ((attributes && parseInt(attributes.index)) || 0);
this._club = ((attributes && parseInt(attributes.club)) || 0);
this._selectable = ((attributes && parseInt(attributes.selectable) === 1) || false);
this._hexCode = ((xml && xml._) || '');
}
public get id(): number
{
return this._id;
}
public get index(): number
{
return this._index;
}
public get club(): number
{
return this._club;
}
public get selectable(): boolean
{
return this._selectable;
}
public get hexCode(): string
{
return this._hexCode;
}
}

View File

@ -0,0 +1,16 @@
export class FigureDataHiddenLayerXML
{
private _partType: string;
constructor(xml: any)
{
const attributes = xml.$;
this._partType = ((attributes && attributes.parttype) || '');
}
public get partType(): string
{
return this._partType;
}
}

View File

@ -0,0 +1,39 @@
import { FigureDataColorXML } from './FigureDataColorXML';
export class FigureDataPaletteXML
{
private _id: number;
private _colors: FigureDataColorXML[];
constructor(xml: any)
{
if(xml.color !== undefined)
{
if(Array.isArray(xml.color))
{
this._colors = [];
for(const col in xml.color)
{
const color = xml.color[col];
this._colors.push(new FigureDataColorXML(color));
}
}
}
const attributes = xml.$;
this._id = ((attributes && parseInt(attributes.id)) || 0);
}
public get id(): number
{
return this._id;
}
public get colors(): FigureDataColorXML[]
{
return this._colors;
}
}

View File

@ -0,0 +1,44 @@
export class FigureDataPartXML
{
private _id: number;
private _type: string;
private _colorable: boolean;
private _index: number;
private _colorindex: number;
constructor(xml: any)
{
const attributes = xml.$;
this._id = ((attributes && parseInt(attributes.id)) || 0);
this._type = ((attributes && attributes.type) || '');
this._colorable = ((attributes && parseInt(attributes.colorable) === 1) || false);
this._index = ((attributes && parseInt(attributes.index)) || 0);
this._colorindex = ((attributes && parseInt(attributes.colorindex)) || 0);
}
public get id(): number
{
return this._id;
}
public get type(): string
{
return this._type;
}
public get colorable(): boolean
{
return this._colorable;
}
public get index(): number
{
return this._index;
}
public get colorIndex(): number
{
return this._colorindex;
}
}

View File

@ -0,0 +1,74 @@
import { FigureDataSetXML } from './FigureDataSetXML';
export class FigureDataSetTypeXML
{
private _type: string;
private _paletteId: number;
private _mandatory_m_0: boolean;
private _mandatory_f_0: boolean;
private _mandatory_m_1: boolean;
private _mandatory_f_1: boolean;
private _sets: FigureDataSetXML[];
constructor(xml: any)
{
const attributes = xml.$;
this._type = ((attributes && attributes.type) || '');
this._paletteId = ((attributes && parseInt(attributes.paletteid)) || 1);
this._mandatory_m_0 = ((attributes && parseInt(attributes.mand_m_0) == 1) || false);
this._mandatory_f_0 = ((attributes && parseInt(attributes.mand_f_0) == 1) || false);
this._mandatory_m_1 = ((attributes && parseInt(attributes.mand_m_1) == 1) || false);
this._mandatory_f_1 = ((attributes && parseInt(attributes.mand_f_1) == 1) || false);
if(xml.set !== undefined)
{
if(Array.isArray(xml.set))
{
this._sets = [];
for(const index in xml.set)
{
const set = xml.set[index];
this._sets.push(new FigureDataSetXML(set));
}
}
}
}
public get type(): string
{
return this._type;
}
public get paletteId(): number
{
return this._paletteId;
}
public get mandatoryM0(): boolean
{
return this._mandatory_m_0;
}
public get mandatoryM1(): boolean
{
return this._mandatory_m_1;
}
public get mandatoryF0(): boolean
{
return this._mandatory_f_0;
}
public get mandatoryF1(): boolean
{
return this._mandatory_f_1;
}
public get sets(): FigureDataSetXML[]
{
return this._sets;
}
}

View File

@ -0,0 +1,103 @@
import { FigureDataHiddenLayerXML } from './FigureDataHiddenLayerXML';
import { FigureDataPartXML } from './FigureDataPartXML';
export class FigureDataSetXML
{
private _id: number;
private _gender: string;
private _club: number;
private _colorable: boolean;
private _selectable: boolean;
private _preselectable: boolean;
private _sellable: boolean;
private _parts: FigureDataPartXML[];
private _hiddenLayers: FigureDataHiddenLayerXML[];
constructor(xml: any)
{
const attributes = xml.$;
this._id = ((attributes && parseInt(attributes.id)) || 0);
this._gender = ((attributes && attributes.gender) || '');
this._club = ((attributes && parseInt(attributes.club)) || 0);
this._colorable = ((attributes && parseInt(attributes.colorable) === 1) || false);
this._selectable = ((attributes && parseInt(attributes.selectable) === 1) || false);
this._preselectable = ((attributes && parseInt(attributes.preselectable) === 1) || false);
this._sellable = ((attributes && parseInt(attributes.sellable) === 1) || false);
if(xml.part !== undefined)
{
if(Array.isArray(xml.part))
{
this._parts = [];
for(const index in xml.part)
{
const part = xml.part[index];
this._parts.push(new FigureDataPartXML(part));
}
}
}
if(xml.hiddenlayers !== undefined)
{
this._hiddenLayers = [];
for(const hiddenLayer of xml.hiddenlayers)
{
const layers = hiddenLayer.layer;
if(layers !== undefined)
{
if(Array.isArray(layers)) for(const layer of layers) this._hiddenLayers.push(new FigureDataHiddenLayerXML(layer));
}
}
}
}
public get id(): number
{
return this._id;
}
public get gender(): string
{
return this._gender;
}
public get club(): number
{
return this._club;
}
public get colorable(): boolean
{
return this._colorable;
}
public get selectable(): boolean
{
return this._selectable;
}
public get preselectable(): boolean
{
return this._preselectable;
}
public get sellable(): boolean
{
return this._sellable;
}
public get parts(): FigureDataPartXML[]
{
return this._parts;
}
public get hiddenLayers(): FigureDataHiddenLayerXML[]
{
return this._hiddenLayers;
}
}

View File

@ -0,0 +1,55 @@
import { FigureDataPaletteXML } from './FigureDataPaletteXML';
import { FigureDataSetTypeXML } from './FigureDataSetTypeXML';
export class FigureDataXML
{
private _colorPalettes: FigureDataPaletteXML[];
private _sets: FigureDataSetTypeXML[];
constructor(xml: any)
{
if(xml.colors !== undefined && xml.colors[0].palette !== undefined)
{
const paletteArr = xml.colors[0].palette;
if(Array.isArray(paletteArr))
{
this._colorPalettes = [];
for(const pal in paletteArr)
{
const palette = paletteArr[pal];
this._colorPalettes.push(new FigureDataPaletteXML(palette));
}
}
}
if(xml.sets !== undefined && xml.sets[0].settype !== undefined)
{
const setTypeArr = xml.sets[0].settype;
if(Array.isArray(setTypeArr))
{
this._sets = [];
for(const set in setTypeArr)
{
const setType = setTypeArr[set];
this._sets.push(new FigureDataSetTypeXML(setType));
}
}
}
}
public get colorPalettes(): FigureDataPaletteXML[]
{
return this._colorPalettes;
}
public get sets(): FigureDataSetTypeXML[]
{
return this._sets;
}
}

View File

@ -0,0 +1,7 @@
export * from './FigureDataColorXML';
export * from './FigureDataHiddenLayerXML';
export * from './FigureDataPaletteXML';
export * from './FigureDataPartXML';
export * from './FigureDataSetTypeXML';
export * from './FigureDataSetXML';
export * from './FigureDataXML';

View File

@ -1,4 +1,5 @@
export * from './asset';
export * from './effectmap';
export * from './figuredata';
export * from './figuremap';
export * from './furnituredata';