mirror of
https://github.com/billsonnn/nitro-imager
synced 2024-11-22 07:40:50 +01:00
Updates
This commit is contained in:
parent
a0400b7a88
commit
2961fc75d4
82
README.md
Normal file
82
README.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Nitro Imager
|
||||
|
||||
This tool serves as a server-side habbo-imager using the same avatar generator from nitro-renderer. It will download & cache in memory ``.nitro`` assets. Rendered figures will also save to a local folder to prevent re-renders. You will use the same process as your nitro-client to update assets for the imager.
|
||||
|
||||
## Configuration
|
||||
|
||||
**Make sure you run ``npm i`` before first use.**
|
||||
|
||||
You must configure your urls in `config.json`
|
||||
|
||||
Your figuredata, figuremap, effectmap, & HabboAvatarActions can safely point to a remote URL without worrying about performance.
|
||||
|
||||
You should set all download urls to local absolute paths on your system, this will allow for faster downloading of figures. However, you may point to remote urls as well.
|
||||
|
||||
You must also set an absolute path to a location where rendered figures can save to. This can be a private folder that is not accessible from the web.
|
||||
|
||||
## URL paramaters
|
||||
|
||||
Their are a few different options you may pass as URL parameters to generate figures with different actions. All parameters are optional.
|
||||
|
||||
| key | default | description |
|
||||
| ------ | ------ | ------ |
|
||||
| figure | null | The figure string to be rendered |
|
||||
| action | null | The actions to render, see actions below |
|
||||
| gesture | std | The gesture to render, see gestures below |
|
||||
| direction | 2 | The direction to render, from 0-7 |
|
||||
| head_direction | 2 | The head direction to render, from 0-7 |
|
||||
| headonly | 0 | A value of ``0`` or ``1`` |
|
||||
| dance | 0 | A dance id of 0-4 to render |
|
||||
| effect | 0 | An effect id to render |
|
||||
| size | n | The size to render, see sizes below |
|
||||
| frame_num | 0 | The frame number to render |
|
||||
| img_format | png | A value of ``png`` or ``gif``. Gif will render all frames of the figure |
|
||||
|
||||
## Actions
|
||||
|
||||
You may render multiple actions with a comma separater
|
||||
|
||||
Example: ``&action=wlk,wav,drk=1``
|
||||
##### Posture
|
||||
| key | description |
|
||||
| ------ | ------ |
|
||||
| std | Renders the standing posture |
|
||||
| wlk,mv | Renders the walking posture |
|
||||
| sit | Renders the sitting posture |
|
||||
| lay | Renders the laying posture |
|
||||
|
||||
##### Expression
|
||||
| key | description |
|
||||
| ------ | ------ |
|
||||
| wav,wave | Renders the waving expression |
|
||||
| blow | Renders the kissing expression |
|
||||
| laugh | Renders the laughing expression |
|
||||
| respect | Renders the respect expression |
|
||||
|
||||
##### Carry / Drink
|
||||
To hold a certain drink, use an equal separator with the hand item id. You can only render one of these options at a time
|
||||
|
||||
| key | description |
|
||||
| ------ | ------ |
|
||||
| crr,cri | Renders the carry action |
|
||||
| drk,usei | Renders the drink action |
|
||||
|
||||
## Gestures
|
||||
| key | description |
|
||||
| ------ | ------ |
|
||||
| std | Renders the standard gesture |
|
||||
| agr | Renders the aggravated gesture |
|
||||
| sad | Renders the sad gesture |
|
||||
| sml | Renders the smile gesture |
|
||||
| srp | Renders the surprised gesture |
|
||||
|
||||
## Sizes
|
||||
| key | description |
|
||||
| ------ | ------ |
|
||||
| s | Renders the small size (0.5) |
|
||||
| n | Renders the normal size (1) |
|
||||
| l | Renders the large size (2) |
|
||||
|
||||
## Known Issues
|
||||
* GIFs are only able to render 1 bit alpha channels, therefore most effects will not correctly render due to using many different alpha values.
|
||||
* The rendered canvas size may not match habbos imager exactly, we will hopefully have this addressed soon.
|
38
config.json
Normal file
38
config.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"api.host": "localhost",
|
||||
"api.port": 3030,
|
||||
"asset.url": "ABSOLUTE_ASSET_URL_WITHOUT_SLASH",
|
||||
"gamedata.url": "${asset.url}/gamedata",
|
||||
"avatar.save.path": "ABSOLUTE_PATH/saved-figures",
|
||||
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json",
|
||||
"avatar.figuredata.url": "${gamedata.url}/FigureData.json",
|
||||
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json",
|
||||
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json",
|
||||
"avatar.asset.url": "${asset.url}/bundled/figure/%libname%.nitro",
|
||||
"avatar.asset.effect.url": "${asset.url}/bundled/effect/%libname%.nitro",
|
||||
"avatar.mandatory.libraries": [
|
||||
"bd:1",
|
||||
"li:0"
|
||||
],
|
||||
"avatar.mandatory.effect.libraries": [
|
||||
"dance.1",
|
||||
"dance.2",
|
||||
"dance.3",
|
||||
"dance.4"
|
||||
],
|
||||
"avatar.default.figuredata": {"palettes":[{"id":1,"colors":[{"id":99999,"index":1001,"club":0,"selectable":false,"hexCode":"DDDDDD"},{"id":99998,"index":1001,"club":0,"selectable":false,"hexCode":"FAFAFA"}]},{"id":3,"colors":[{"id":10001,"index":1001,"club":0,"selectable":false,"hexCode":"EEEEEE"},{"id":10002,"index":1002,"club":0,"selectable":false,"hexCode":"FA3831"},{"id":10003,"index":1003,"club":0,"selectable":false,"hexCode":"FD92A0"},{"id":10004,"index":1004,"club":0,"selectable":false,"hexCode":"2AC7D2"},{"id":10005,"index":1005,"club":0,"selectable":false,"hexCode":"35332C"},{"id":10006,"index":1006,"club":0,"selectable":false,"hexCode":"EFFF92"},{"id":10007,"index":1007,"club":0,"selectable":false,"hexCode":"C6FF98"},{"id":10008,"index":1008,"club":0,"selectable":false,"hexCode":"FF925A"},{"id":10009,"index":1009,"club":0,"selectable":false,"hexCode":"9D597E"},{"id":10010,"index":1010,"club":0,"selectable":false,"hexCode":"B6F3FF"},{"id":10011,"index":1011,"club":0,"selectable":false,"hexCode":"6DFF33"},{"id":10012,"index":1012,"club":0,"selectable":false,"hexCode":"3378C9"},{"id":10013,"index":1013,"club":0,"selectable":false,"hexCode":"FFB631"},{"id":10014,"index":1014,"club":0,"selectable":false,"hexCode":"DFA1E9"},{"id":10015,"index":1015,"club":0,"selectable":false,"hexCode":"F9FB32"},{"id":10016,"index":1016,"club":0,"selectable":false,"hexCode":"CAAF8F"},{"id":10017,"index":1017,"club":0,"selectable":false,"hexCode":"C5C6C5"},{"id":10018,"index":1018,"club":0,"selectable":false,"hexCode":"47623D"},{"id":10019,"index":1019,"club":0,"selectable":false,"hexCode":"8A8361"},{"id":10020,"index":1020,"club":0,"selectable":false,"hexCode":"FF8C33"},{"id":10021,"index":1021,"club":0,"selectable":false,"hexCode":"54C627"},{"id":10022,"index":1022,"club":0,"selectable":false,"hexCode":"1E6C99"},{"id":10023,"index":1023,"club":0,"selectable":false,"hexCode":"984F88"},{"id":10024,"index":1024,"club":0,"selectable":false,"hexCode":"77C8FF"},{"id":10025,"index":1025,"club":0,"selectable":false,"hexCode":"FFC08E"},{"id":10026,"index":1026,"club":0,"selectable":false,"hexCode":"3C4B87"},{"id":10027,"index":1027,"club":0,"selectable":false,"hexCode":"7C2C47"},{"id":10028,"index":1028,"club":0,"selectable":false,"hexCode":"D7FFE3"},{"id":10029,"index":1029,"club":0,"selectable":false,"hexCode":"8F3F1C"},{"id":10030,"index":1030,"club":0,"selectable":false,"hexCode":"FF6393"},{"id":10031,"index":1031,"club":0,"selectable":false,"hexCode":"1F9B79"},{"id":10032,"index":1032,"club":0,"selectable":false,"hexCode":"FDFF33"}]}],"setTypes":[{"type":"hd","paletteId":1,"mandatory_f_0":true,"mandatory_f_1":true,"mandatory_m_0":true,"mandatory_m_1":true,"sets":[{"id":99999,"gender":"U","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":1,"type":"bd","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"hd","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"lh","colorable":true,"index":0,"colorindex":1},{"id":1,"type":"rh","colorable":true,"index":0,"colorindex":1}]}]},{"type":"bds","paletteId":1,"mandatory_f_0":false,"mandatory_f_1":false,"mandatory_m_0":false,"mandatory_m_1":false,"sets":[{"id":10001,"gender":"U","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10001,"type":"bds","colorable":true,"index":0,"colorindex":1},{"id":10001,"type":"lhs","colorable":true,"index":0,"colorindex":1},{"id":10001,"type":"rhs","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"bd"},{"partType":"rh"},{"partType":"lh"}]}]},{"type":"ss","paletteId":3,"mandatory_f_0":false,"mandatory_f_1":false,"mandatory_m_0":false,"mandatory_m_1":false,"sets":[{"id":10010,"gender":"F","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10001,"type":"ss","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"ch"},{"partType":"lg"},{"partType":"ca"},{"partType":"wa"},{"partType":"sh"},{"partType":"ls"},{"partType":"rs"},{"partType":"lc"},{"partType":"rc"},{"partType":"cc"},{"partType":"cp"}]},{"id":10011,"gender":"M","club":0,"colorable":true,"selectable":false,"preselectable":false,"sellable":false,"parts":[{"id":10002,"type":"ss","colorable":true,"index":0,"colorindex":1}],"hiddenLayers":[{"partType":"ch"},{"partType":"lg"},{"partType":"ca"},{"partType":"wa"},{"partType":"sh"},{"partType":"ls"},{"partType":"rs"},{"partType":"lc"},{"partType":"rc"},{"partType":"cc"},{"partType":"cp"}]}]}]},
|
||||
"avatar.default.actions": {
|
||||
"actions": [
|
||||
{
|
||||
"id": "Default",
|
||||
"state": "std",
|
||||
"precedence": 1000,
|
||||
"main": true,
|
||||
"isDefault": true,
|
||||
"geometryType": "vertical",
|
||||
"activePartSet": "figure",
|
||||
"assetPartDefinition": "std"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -81,6 +81,15 @@
|
||||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/gifencoder": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/gifencoder/-/gifencoder-2.0.1.tgz",
|
||||
"integrity": "sha512-Ls78JLiLPHA1ytIXMWv/7/71a2Cz7BBnjgi9R/LFcIS531PEFYxPPGHNmBBnLekQ7/VpO+n1fgaJ6XD3ZkpApg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/long": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
|
||||
@ -464,11 +473,6 @@
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
|
||||
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
|
||||
},
|
||||
"dynamic-dedupe": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
|
||||
@ -620,6 +624,14 @@
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"gifencoder": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gifencoder/-/gifencoder-2.0.1.tgz",
|
||||
"integrity": "sha512-x19DcyWY10SkshBpokqFOo/HBht9GB75evRYvaLMbez9p+yB/o+kt0fK9AwW59nFiAMs2UUQsjv1lX/hvu9Ong==",
|
||||
"requires": {
|
||||
"canvas": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||
|
@ -2,13 +2,13 @@
|
||||
"name": "nitro-imager",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "index.ts",
|
||||
"dependencies": {
|
||||
"bytebuffer": "^5.0.1",
|
||||
"canvas": "^2.8.0",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"gifencoder": "^2.0.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pako": "^2.0.4"
|
||||
},
|
||||
@ -16,6 +16,7 @@
|
||||
"@types/bytebuffer": "^5.0.42",
|
||||
"@types/chalk": "^2.2.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/gifencoder": "^2.0.1",
|
||||
"@types/node": "^14.17.12",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/pako": "^1.0.2",
|
||||
@ -23,6 +24,8 @@
|
||||
"typescript": "^4.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node ./dist/index.js",
|
||||
"start:dev": "ts-node-dev --respawn --transpile-only ./src/main.ts"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as express from 'express';
|
||||
import { INitroCore, NitroManager } from '../core';
|
||||
import { AvatarRenderManager, AvatarScaleType, AvatarSetType, IAvatarRenderManager } from './avatar';
|
||||
import { AvatarRenderManager, IAvatarRenderManager } from './avatar';
|
||||
import { IApplication } from './IApplication';
|
||||
import { HttpRouter } from './router/HttpRouter';
|
||||
|
||||
export class Application extends NitroManager implements IApplication
|
||||
{
|
||||
@ -25,11 +27,7 @@ export class Application extends NitroManager implements IApplication
|
||||
|
||||
if(this._avatar) await this._avatar.init();
|
||||
|
||||
const image = await this._avatar.createAvatarImage('hd-207-14.lg-3216-1408.cc-3007-86-88.ha-3054-1408-1408.he-3079-64.ea-1402-0.ch-230-72.hr-110-40', AvatarScaleType.LARGE, 'M');
|
||||
|
||||
//image.setDirection(AvatarSetType.FULL, 4);
|
||||
const canvas = await image.getImage(AvatarSetType.FULL, false);
|
||||
console.log(canvas.toDataURL());
|
||||
this.setupRouter();
|
||||
|
||||
this.logger.log(`Initialized`);
|
||||
}
|
||||
@ -46,6 +44,21 @@ export class Application extends NitroManager implements IApplication
|
||||
return this._core.configuration.getValue<T>(key, value);
|
||||
}
|
||||
|
||||
private setupRouter(): void
|
||||
{
|
||||
const router = express();
|
||||
|
||||
router.use('/', HttpRouter);
|
||||
|
||||
const host = this.getConfiguration<string>('api.host');
|
||||
const port = this.getConfiguration<number>('api.port');
|
||||
|
||||
router.listen(port, host, () =>
|
||||
{
|
||||
this.logger.log(`Server Started ${ host }:${ port }`);
|
||||
});
|
||||
}
|
||||
|
||||
public get core(): INitroCore
|
||||
{
|
||||
return this._core;
|
||||
|
@ -42,17 +42,19 @@ export class AvatarAssetDownloadLibrary
|
||||
return false;
|
||||
}
|
||||
|
||||
public async downloadAsset(): Promise<void>
|
||||
public async downloadAsset(): Promise<boolean>
|
||||
{
|
||||
if(!this._assets || (this._state === AvatarAssetDownloadLibrary.LOADING)) return;
|
||||
if(!this._assets || (this._state === AvatarAssetDownloadLibrary.LOADING)) return false;
|
||||
|
||||
if(this.checkIfAssetLoaded()) return;
|
||||
if(this.checkIfAssetLoaded()) return false;
|
||||
|
||||
this._state = AvatarAssetDownloadLibrary.LOADING;
|
||||
|
||||
await this._assets.downloadAsset(this._downloadUrl);
|
||||
if(!await this._assets.downloadAsset(this._downloadUrl)) return false;
|
||||
|
||||
this._state = AvatarAssetDownloadLibrary.LOADED;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get libraryName(): string
|
||||
|
@ -1,5 +1,4 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { AdvancedMap, IAssetManager } from '../../core';
|
||||
import { AdvancedMap, FileUtilities, IAssetManager } from '../../core';
|
||||
import { Application } from '../Application';
|
||||
import { AvatarAssetDownloadLibrary } from './AvatarAssetDownloadLibrary';
|
||||
import { AvatarStructure } from './AvatarStructure';
|
||||
@ -28,8 +27,8 @@ export class AvatarAssetDownloadManager
|
||||
{
|
||||
const url = Application.instance.getConfiguration<string>('avatar.figuremap.url');
|
||||
|
||||
const data = await fetch(url);
|
||||
const json = await data.json();
|
||||
const data = await FileUtilities.readFileAsString(url);
|
||||
const json = JSON.parse(data);
|
||||
|
||||
this.processFigureMap(json.libraries);
|
||||
|
||||
@ -149,6 +148,6 @@ export class AvatarAssetDownloadManager
|
||||
{
|
||||
if(!library || library.isLoaded) return;
|
||||
|
||||
await library.downloadAsset();
|
||||
if(!await library.downloadAsset()) return;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Canvas, createCanvas } from 'canvas';
|
||||
import { IGraphicAsset } from '../../core';
|
||||
import { Canvas } from 'canvas';
|
||||
import { CanvasUtilities, IGraphicAsset } from '../../core';
|
||||
import { ActiveActionData, IActionDefinition, IActiveActionData } from './actions';
|
||||
import { AssetAliasCollection } from './alias';
|
||||
import { IAnimationLayerData, IAvatarDataContainer, ISpriteDataContainer } from './animation';
|
||||
@ -168,7 +168,7 @@ export class AvatarImage implements IAvatarImage
|
||||
|
||||
public getCanvasOffsets(): number[]
|
||||
{
|
||||
return this._canvasOffsets;
|
||||
return (this._canvasOffsets || [ 0, 0, 0 ]);
|
||||
}
|
||||
|
||||
public getLayerData(k: ISpriteDataContainer): IAnimationLayerData
|
||||
@ -186,6 +186,26 @@ export class AvatarImage implements IAvatarImage
|
||||
this._frameCounter = 0;
|
||||
}
|
||||
|
||||
public getTotalFrameCount(): number
|
||||
{
|
||||
const actions = this._sortedActions;
|
||||
|
||||
let frames = this._animationFrameCount;
|
||||
|
||||
for(const action of actions)
|
||||
{
|
||||
const animation = this._structure.animationManager.getAnimation(((action.definition.state + '.') + action.actionParameter));
|
||||
|
||||
if(!animation) continue;
|
||||
|
||||
const frameCount = animation.frameCount(action.overridingAction);
|
||||
|
||||
frames = Math.max(frames, frameCount);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
private getFullImageCacheKey(): string
|
||||
{
|
||||
if(((this._sortedActions.length == 1) && (this._mainDirection == this._headDirection)))
|
||||
@ -257,7 +277,7 @@ export class AvatarImage implements IAvatarImage
|
||||
}
|
||||
}
|
||||
|
||||
public async getImage(setType: string, hightlight: boolean, scale: number = 1): Promise<Canvas>
|
||||
public async getImage(setType: string, bgColor: number = 0, hightlight: boolean = false, scale: number = 1): Promise<Canvas>
|
||||
{
|
||||
if(!this._mainAction) return null;
|
||||
|
||||
@ -269,9 +289,16 @@ export class AvatarImage implements IAvatarImage
|
||||
|
||||
const bodyParts = this.getBodyParts(setType, this._mainAction.definition.geometryType, this._mainDirection);
|
||||
|
||||
const canvas = createCanvas(avatarCanvas.width, avatarCanvas.height);
|
||||
const canvas = CanvasUtilities.createNitroCanvas(avatarCanvas.width, avatarCanvas.height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if(bgColor > 0)
|
||||
{
|
||||
ctx.fillStyle = ctx.fillStyle = `#${(`00000${(bgColor | 0).toString(16)}`).substr(-6)}`;
|
||||
ctx.fillRect(0, 0, avatarCanvas.width, avatarCanvas.height);
|
||||
ctx.fillStyle = null;
|
||||
}
|
||||
|
||||
let partCount = (bodyParts.length - 1);
|
||||
|
||||
while(partCount >= 0)
|
||||
@ -296,7 +323,6 @@ export class AvatarImage implements IAvatarImage
|
||||
point.y += avatarCanvas.regPoint.y;
|
||||
|
||||
ctx.save();
|
||||
ctx.scale(scale, 1);
|
||||
ctx.drawImage(part.image, point.x, point.y, part.image.width, part.image.height);
|
||||
ctx.restore();
|
||||
}
|
||||
@ -305,6 +331,11 @@ export class AvatarImage implements IAvatarImage
|
||||
partCount--;
|
||||
}
|
||||
|
||||
if(scale !== 1) return CanvasUtilities.scaleCanvas(canvas, scale, scale);
|
||||
|
||||
// canvas.width *= scale;
|
||||
// canvas.height *= scale;
|
||||
|
||||
//CanvasUtilities.cropTransparentPixels(canvas);
|
||||
|
||||
//if(this._avatarSpriteData && this._avatarSpriteData.paletteIsGrayscale) this.convertToGrayscale(container);
|
||||
@ -362,7 +393,10 @@ export class AvatarImage implements IAvatarImage
|
||||
{
|
||||
if(k.actionType === AvatarAction.EFFECT)
|
||||
{
|
||||
if(!this._effectManager.isAvatarEffectReady(parseInt(k.actionParameter))) await this._effectManager.downloadAvatarEffect(parseInt(k.actionParameter));
|
||||
if(!this._effectManager.isAvatarEffectReady(parseInt(k.actionParameter)))
|
||||
{
|
||||
await this._effectManager.downloadAvatarEffect(parseInt(k.actionParameter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,7 +614,7 @@ export class AvatarImage implements IAvatarImage
|
||||
{
|
||||
if(!this._sortedActions == null) return;
|
||||
|
||||
const _local_3: number = Date.now();
|
||||
const _local_3: number = 0;
|
||||
const _local_4: string[] = [];
|
||||
|
||||
for(const k of this._sortedActions) _local_4.push(k.actionType);
|
||||
@ -736,8 +770,8 @@ export class AvatarImage implements IAvatarImage
|
||||
return this._animationHasResetOnToggle;
|
||||
}
|
||||
|
||||
public get mainAction(): string
|
||||
public get mainAction(): IActiveActionData
|
||||
{
|
||||
return this._mainAction.actionType;
|
||||
return this._mainAction;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { IAssetManager, IGraphicAsset, NitroManager } from '../../core';
|
||||
import { FileUtilities, IAssetManager, IGraphicAsset, NitroManager } from '../../core';
|
||||
import { Application } from '../Application';
|
||||
import { AssetAliasCollection } from './alias';
|
||||
import { AvatarAssetDownloadManager } from './AvatarAssetDownloadManager';
|
||||
@ -18,7 +17,7 @@ import { IFigurePartSet, IStructureData } from './structure';
|
||||
|
||||
export class AvatarRenderManager extends NitroManager implements IAvatarRenderManager
|
||||
{
|
||||
private static DEFAULT_FIGURE: string = 'hd-99999-99999';
|
||||
public static DEFAULT_FIGURE: string = 'hd-99999-99999';
|
||||
|
||||
private _aliasCollection: AssetAliasCollection;
|
||||
|
||||
@ -87,9 +86,9 @@ export class AvatarRenderManager extends NitroManager implements IAvatarRenderMa
|
||||
|
||||
const url = Application.instance.getConfiguration<string>('avatar.actions.url');
|
||||
|
||||
const data = await fetch(url);
|
||||
const data = await FileUtilities.readFileAsString(url);
|
||||
|
||||
this._structure.updateActions(await data.json());
|
||||
this._structure.updateActions(JSON.parse(data));
|
||||
}
|
||||
|
||||
private async loadFigureData(): Promise<void>
|
||||
@ -100,9 +99,9 @@ export class AvatarRenderManager extends NitroManager implements IAvatarRenderMa
|
||||
|
||||
const url = Application.instance.getConfiguration<string>('avatar.figuredata.url');
|
||||
|
||||
const data = await fetch(url);
|
||||
const data = await FileUtilities.readFileAsString(url);
|
||||
|
||||
this._structure.figureData.appendJSON(await data.json());
|
||||
this._structure.figureData.appendJSON(JSON.parse(data));
|
||||
}
|
||||
|
||||
public createFigureContainer(figure: string): IAvatarFigureContainer
|
||||
@ -307,4 +306,9 @@ export class AvatarRenderManager extends NitroManager implements IAvatarRenderMa
|
||||
{
|
||||
return this._avatarAssetDownloadManager;
|
||||
}
|
||||
|
||||
public get effectManager(): EffectAssetDownloadManager
|
||||
{
|
||||
return this._effectAssetDownloadManager;
|
||||
}
|
||||
}
|
||||
|
@ -44,21 +44,23 @@ export class EffectAssetDownloadLibrary
|
||||
return false;
|
||||
}
|
||||
|
||||
public async downloadAsset(): Promise<void>
|
||||
public async downloadAsset(): Promise<boolean>
|
||||
{
|
||||
if(!this._assets || (this._state === EffectAssetDownloadLibrary.LOADING)) return;
|
||||
|
||||
if(this.checkIfAssetLoaded()) return;
|
||||
if(this.checkIfAssetLoaded()) return true;
|
||||
|
||||
this._state = EffectAssetDownloadLibrary.LOADING;
|
||||
|
||||
await this._assets.downloadAsset(this._downloadUrl);
|
||||
if(!await this._assets.downloadAsset(this._downloadUrl)) return false;
|
||||
|
||||
const collection = this._assets.getCollection(this._libraryName);
|
||||
|
||||
if(collection) this._animation = collection.data.animations;
|
||||
|
||||
this._state = EffectAssetDownloadLibrary.LOADED;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get libraryName(): string
|
||||
|
@ -1,5 +1,4 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { AdvancedMap, IAssetManager } from '../../core';
|
||||
import { AdvancedMap, FileUtilities, IAssetManager } from '../../core';
|
||||
import { Application } from '../Application';
|
||||
import { AvatarStructure } from './AvatarStructure';
|
||||
import { EffectAssetDownloadLibrary } from './EffectAssetDownloadLibrary';
|
||||
@ -27,8 +26,8 @@ export class EffectAssetDownloadManager
|
||||
{
|
||||
const url = Application.instance.getConfiguration<string>('avatar.effectmap.url');
|
||||
|
||||
const data = await fetch(url);
|
||||
const json = await data.json();
|
||||
const data = await FileUtilities.readFileAsString(url);
|
||||
const json = JSON.parse(data);
|
||||
|
||||
this.processEffectMap(json.effects);
|
||||
|
||||
@ -118,6 +117,8 @@ export class EffectAssetDownloadManager
|
||||
{
|
||||
if(!library || library.isLoaded) return;
|
||||
|
||||
await library.downloadAsset();
|
||||
if(!await library.downloadAsset()) return;
|
||||
|
||||
this._structure.registerAnimation(library.animation);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Canvas } from 'canvas';
|
||||
import { IDisposable, IGraphicAsset } from '../../core';
|
||||
import { IActiveActionData } from './actions';
|
||||
import { IAnimationLayerData, IAvatarDataContainer, ISpriteDataContainer } from './animation';
|
||||
import { IAvatarFigureContainer } from './IAvatarFigureContainer';
|
||||
import { IPartColor } from './structure';
|
||||
@ -12,7 +13,7 @@ export interface IAvatarImage extends IDisposable
|
||||
getScale(): string;
|
||||
getSprites(): ISpriteDataContainer[];
|
||||
getLayerData(_arg_1: ISpriteDataContainer): IAnimationLayerData;
|
||||
getImage(setType: string, hightlight: boolean, scale?: number, cache?: boolean): Promise<Canvas>;
|
||||
getImage(setType: string, bgColor?: number, hightlight?: boolean, scale?: number, cache?: boolean): Promise<Canvas>;
|
||||
getAsset(_arg_1: string): IGraphicAsset;
|
||||
getDirection(): number;
|
||||
getFigure(): IAvatarFigureContainer;
|
||||
@ -27,5 +28,6 @@ export interface IAvatarImage extends IDisposable
|
||||
forceActionUpdate(): void;
|
||||
animationHasResetOnToggle: boolean;
|
||||
resetAnimationFrameCounter(): void;
|
||||
mainAction: string;
|
||||
mainAction: IActiveActionData;
|
||||
getTotalFrameCount(): number;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { IAssetManager, IGraphicAsset, INitroManager } from '../../core';
|
||||
import { AvatarAssetDownloadManager } from './AvatarAssetDownloadManager';
|
||||
import { AvatarStructure } from './AvatarStructure';
|
||||
import { EffectAssetDownloadManager } from './EffectAssetDownloadManager';
|
||||
import { IAvatarFigureContainer } from './IAvatarFigureContainer';
|
||||
import { IAvatarImage } from './IAvatarImage';
|
||||
import { IStructureData } from './structure/IStructureData';
|
||||
@ -20,4 +21,5 @@ export interface IAvatarRenderManager extends INitroManager
|
||||
structure: AvatarStructure;
|
||||
structureData: IStructureData;
|
||||
downloadManager: AvatarAssetDownloadManager;
|
||||
effectManager: EffectAssetDownloadManager;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export class AvatarImageActionCache
|
||||
{
|
||||
this._cache = new AdvancedMap();
|
||||
|
||||
this.setLastAccessTime(Date.now());
|
||||
this.setLastAccessTime(0);
|
||||
}
|
||||
|
||||
public dispose(): void
|
||||
|
11
src/app/avatar/cache/AvatarImageCache.ts
vendored
11
src/app/avatar/cache/AvatarImageCache.ts
vendored
@ -1,5 +1,4 @@
|
||||
import { createCanvas } from 'canvas';
|
||||
import { AdvancedMap, Point, Rectangle } from '../../../core';
|
||||
import { AdvancedMap, CanvasUtilities, Point, Rectangle } from '../../../core';
|
||||
import { IActiveActionData } from '../actions';
|
||||
import { AssetAliasCollection } from '../alias';
|
||||
import { AvatarAnimationLayerData } from '../animation';
|
||||
@ -78,7 +77,7 @@ export class AvatarImageCache
|
||||
|
||||
public disposeInactiveActions(k: number = 60000): void
|
||||
{
|
||||
const time = Date.now();
|
||||
const time = 0;
|
||||
|
||||
if(this._cache)
|
||||
{
|
||||
@ -437,7 +436,7 @@ export class AvatarImageCache
|
||||
for(const data of imageDatas) data && bounds.enlarge(data.offsetRect);
|
||||
|
||||
const point = new Point(-(bounds.x), -(bounds.y));
|
||||
const canvas = createCanvas(bounds.width, bounds.height);
|
||||
const canvas = CanvasUtilities.createNitroCanvas(bounds.width, bounds.height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
for(const data of imageDatas)
|
||||
@ -477,10 +476,6 @@ export class AvatarImageCache
|
||||
ctx.transform(scale, 0, 0, 1, tx, ty);
|
||||
ctx.drawImage(tintedTexture, 0, 0, data.rect.width, data.rect.height);
|
||||
ctx.restore();
|
||||
|
||||
// set the color
|
||||
//console.log(canvas.toDataURL());
|
||||
//console.log();
|
||||
}
|
||||
|
||||
return new CompleteImageData(canvas, new Rectangle(0, 0, canvas.width, canvas.height), point, isFlipped, null);
|
||||
|
@ -12,17 +12,17 @@ export const HabboAvatarGeometry = {
|
||||
'geometries': [
|
||||
{
|
||||
'id': 'vertical',
|
||||
'width': 90,
|
||||
'height': 130,
|
||||
'width': 64,
|
||||
'height': 110,
|
||||
'dx': 0,
|
||||
'dy': 0
|
||||
'dy': 6
|
||||
},
|
||||
{
|
||||
'id': 'sitting',
|
||||
'width': 90,
|
||||
'height': 130,
|
||||
'width': 64,
|
||||
'height': 110,
|
||||
'dx': 0,
|
||||
'dy': 0
|
||||
'dy': 6
|
||||
},
|
||||
{
|
||||
'id': 'horizontal',
|
||||
|
6
src/app/router/HttpRouter.ts
Normal file
6
src/app/router/HttpRouter.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { HabboImagingRouter } from './habbo-imaging';
|
||||
|
||||
export const HttpRouter = Router();
|
||||
|
||||
HttpRouter.use('/', HabboImagingRouter);
|
6
src/app/router/habbo-imaging/HabboImagingRouter.ts
Normal file
6
src/app/router/habbo-imaging/HabboImagingRouter.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { HabboImagingRouterGet } from './handlers';
|
||||
|
||||
export const HabboImagingRouter = Router();
|
||||
|
||||
HabboImagingRouter.get('/', HabboImagingRouterGet);
|
236
src/app/router/habbo-imaging/handlers/HabboImagingRouterGet.ts
Normal file
236
src/app/router/habbo-imaging/handlers/HabboImagingRouterGet.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import { Canvas, createCanvas } from 'canvas';
|
||||
import { Request, Response } from 'express';
|
||||
import { createWriteStream, writeFile, WriteStream } from 'fs';
|
||||
import * as GIFEncoder from 'gifencoder';
|
||||
import { File, FileUtilities, Point } from '../../../../core';
|
||||
import { Application } from '../../../Application';
|
||||
import { AvatarScaleType, IAvatarImage } from '../../../avatar';
|
||||
import { BuildFigureOptionsRequest, BuildFigureOptionsStringRequest, ProcessActionRequest, ProcessDanceRequest, ProcessDirectionRequest, ProcessEffectRequest, ProcessGestureRequest, RequestQuery } from './utils';
|
||||
|
||||
export const HabboImagingRouterGet = async (request: Request<any, any, any, RequestQuery>, response: Response) =>
|
||||
{
|
||||
const query = request.query;
|
||||
|
||||
try
|
||||
{
|
||||
const buildOptions = BuildFigureOptionsRequest(query);
|
||||
const saveDirectory = Application.instance.getConfiguration<string>('avatar.save.path');
|
||||
const directory = FileUtilities.getDirectory(saveDirectory);
|
||||
const avatarString = BuildFigureOptionsStringRequest(buildOptions);
|
||||
const saveFile = new File(`${ directory.path }/${ avatarString }.${ buildOptions.imageFormat }`);
|
||||
|
||||
if(saveFile.exists())
|
||||
{
|
||||
const buffer = await FileUtilities.readFileAsBuffer(saveFile.path);
|
||||
|
||||
if(buffer)
|
||||
{
|
||||
response
|
||||
.writeHead(200, {
|
||||
'Content-Type': ((buildOptions.imageFormat === 'gif') ? 'image/gif' : 'image/png')
|
||||
})
|
||||
.end(buffer);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(buildOptions.effect > 0)
|
||||
{
|
||||
if(!Application.instance.avatar.effectManager.isAvatarEffectReady(buildOptions.effect))
|
||||
{
|
||||
await Application.instance.avatar.effectManager.downloadAvatarEffect(buildOptions.effect);
|
||||
}
|
||||
}
|
||||
|
||||
const avatar = await Application.instance.avatar.createAvatarImage(buildOptions.figure, AvatarScaleType.LARGE, 'M');
|
||||
const avatarCanvas = Application.instance.avatar.structure.getCanvas(avatar.getScale(), avatar.mainAction.definition.geometryType);
|
||||
|
||||
ProcessDirectionRequest(query, avatar);
|
||||
|
||||
avatar.initActionAppends();
|
||||
|
||||
ProcessActionRequest(query, avatar);
|
||||
ProcessGestureRequest(query, avatar);
|
||||
ProcessDanceRequest(query, avatar);
|
||||
ProcessEffectRequest(query, avatar);
|
||||
|
||||
avatar.endActionAppends();
|
||||
|
||||
const bgColor = 376510773; // magenta
|
||||
|
||||
const tempCanvas = createCanvas((avatarCanvas.width * buildOptions.size), (avatarCanvas.height * buildOptions.size));
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
let encoder: GIFEncoder = null;
|
||||
let stream: WriteStream = null;
|
||||
|
||||
if(buildOptions.imageFormat === 'gif')
|
||||
{
|
||||
encoder = new GIFEncoder(tempCanvas.width, tempCanvas.height);
|
||||
stream = encoder.createReadStream().pipe(createWriteStream(saveFile.path));
|
||||
|
||||
encoder.setTransparent(bgColor);
|
||||
encoder.start();
|
||||
encoder.setRepeat(0);
|
||||
encoder.setDelay(1);
|
||||
encoder.setQuality(10);
|
||||
}
|
||||
|
||||
let totalFrames = 0;
|
||||
|
||||
if(buildOptions.imageFormat !== 'gif')
|
||||
{
|
||||
if(buildOptions.frameNumber > 0) avatar.updateAnimationByFrames(buildOptions.frameNumber);
|
||||
|
||||
totalFrames = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalFrames = ((avatar.getTotalFrameCount() * 2) || 1);
|
||||
}
|
||||
|
||||
for(let i = 0; i < totalFrames; i++)
|
||||
{
|
||||
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
if(totalFrames && (i > 0)) avatar.updateAnimationByFrames(1);
|
||||
|
||||
const canvas = await avatar.getImage(buildOptions.setType, 0, false, buildOptions.size);
|
||||
|
||||
const avatarOffset = new Point();
|
||||
const canvasOffset = new Point();
|
||||
|
||||
canvasOffset.x = ((tempCanvas.width - canvas.width) / 2);
|
||||
canvasOffset.y = ((tempCanvas.height - canvas.height) / 2);
|
||||
|
||||
for(const sprite of avatar.getSprites())
|
||||
{
|
||||
if(sprite.id === 'avatar')
|
||||
{
|
||||
const layerData = avatar.getLayerData(sprite);
|
||||
|
||||
avatarOffset.x = sprite.getDirectionOffsetX(buildOptions.direction);
|
||||
avatarOffset.y = sprite.getDirectionOffsetY(buildOptions.direction);
|
||||
|
||||
if(layerData)
|
||||
{
|
||||
avatarOffset.x += layerData.dx;
|
||||
avatarOffset.y += layerData.dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avatarSize = 64;
|
||||
const sizeOffset = new Point(((canvas.width - avatarSize) / 2), (canvas.height - (avatarSize / 4)));
|
||||
|
||||
ProcessAvatarSprites(tempCanvas, avatar, avatarOffset, canvasOffset.add(sizeOffset), false);
|
||||
tempCtx.drawImage(canvas, avatarOffset.x, avatarOffset.y, canvas.width, canvas.height);
|
||||
ProcessAvatarSprites(tempCanvas, avatar, avatarOffset, canvasOffset.add(sizeOffset), true);
|
||||
|
||||
if(encoder)
|
||||
{
|
||||
encoder.addFrame(tempCtx);
|
||||
}
|
||||
else
|
||||
{
|
||||
const buffer = tempCanvas.toBuffer();
|
||||
|
||||
response
|
||||
.writeHead(200, {
|
||||
'Content-Type': 'image/png'
|
||||
})
|
||||
.end(buffer);
|
||||
|
||||
writeFile(saveFile.path, buffer, () => {});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(encoder) encoder.finish();
|
||||
|
||||
if(stream)
|
||||
{
|
||||
await new Promise((resolve, reject) =>
|
||||
{
|
||||
stream.on('finish', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = await FileUtilities.readFileAsBuffer(saveFile.path);
|
||||
|
||||
response
|
||||
.writeHead(200, {
|
||||
'Content-Type': 'image/gif'
|
||||
})
|
||||
.end(buffer);
|
||||
}
|
||||
|
||||
catch(err)
|
||||
{
|
||||
Application.instance.logger.error(err.message);
|
||||
|
||||
response
|
||||
.writeHead(500)
|
||||
.end();
|
||||
}
|
||||
}
|
||||
|
||||
function ProcessAvatarSprites(canvas: Canvas, avatar: IAvatarImage, avatarOffset: Point, canvasOffset: Point, frontSprites: boolean = true)
|
||||
{
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
for(const sprite of avatar.getSprites())
|
||||
{
|
||||
if(sprite.id === 'avatar') continue;
|
||||
|
||||
const layerData = avatar.getLayerData(sprite);
|
||||
|
||||
let offsetX = sprite.getDirectionOffsetX(avatar.getDirection());
|
||||
let offsetY = sprite.getDirectionOffsetY(avatar.getDirection());
|
||||
let offsetZ = sprite.getDirectionOffsetZ(avatar.getDirection());
|
||||
let direction = 0;
|
||||
let frame = 0;
|
||||
|
||||
if(!frontSprites)
|
||||
{
|
||||
if(offsetZ >= 0) continue;
|
||||
}
|
||||
else if(offsetZ < 0) continue;
|
||||
|
||||
if(sprite.hasDirections) direction = avatar.getDirection();
|
||||
|
||||
if(layerData)
|
||||
{
|
||||
frame = layerData.animationFrame;
|
||||
offsetX = (offsetX + layerData.dx);
|
||||
offsetY = (offsetY + layerData.dy);
|
||||
direction = (direction + layerData.dd);
|
||||
}
|
||||
|
||||
if(direction < 0) direction = (direction + 8);
|
||||
|
||||
if(direction > 7) direction = (direction - 8);
|
||||
|
||||
const assetName = ((((((avatar.getScale() + "_") + sprite.member) + "_") + direction) + "_") + frame);
|
||||
const asset = avatar.getAsset(assetName);
|
||||
|
||||
if(!asset) continue;
|
||||
|
||||
const texture = asset.texture;
|
||||
|
||||
let x = ((canvasOffset.x - (1 * asset.offsetX)) + offsetX);
|
||||
let y = ((canvasOffset.y - (1 * asset.offsetY)) + offsetY);
|
||||
|
||||
ctx.save();
|
||||
|
||||
if(sprite.ink === 33) ctx.globalCompositeOperation = 'lighter';
|
||||
|
||||
ctx.transform(1, 0, 0, 1, (x - avatarOffset.x), (y - avatarOffset.y));
|
||||
ctx.drawImage(texture.drawableCanvas, 0, 0, texture.width, texture.height);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
1
src/app/router/habbo-imaging/handlers/index.ts
Normal file
1
src/app/router/habbo-imaging/handlers/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './HabboImagingRouterGet';
|
@ -0,0 +1,42 @@
|
||||
import { GetActionRequest } from './GetActionRequest';
|
||||
import { GetDanceRequest } from './GetDanceRequest';
|
||||
import { GetDirectionRequest } from './GetDirectionRequest';
|
||||
import { GetEffectRequest } from './GetEffectRequest';
|
||||
import { GetFigureRequest } from './GetFigureRequest';
|
||||
import { GetFrameNumberRequest } from './GetFrameNumberRequest';
|
||||
import { GetGestureRequest } from './GetGestureRequest';
|
||||
import { GetHeadDirectionRequest } from './GetHeadDirectionRequest';
|
||||
import { GetImageFormatRequest } from './GetImageFormatRequest';
|
||||
import { GetSetTypeRequest } from './GetSetTypeRequest';
|
||||
import { GetSizeRequest } from './GetSizeRequest';
|
||||
import { IFigureBuildOptions } from './IFigureBuildOptions';
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const BuildFigureOptionsRequest = (query: RequestQuery) =>
|
||||
{
|
||||
const figure = GetFigureRequest(query);
|
||||
const size = GetSizeRequest(query);
|
||||
const setType = GetSetTypeRequest(query);
|
||||
const direction = (GetDirectionRequest(query) || 2);
|
||||
const headDirection = (GetHeadDirectionRequest(query) || direction);
|
||||
const action = GetActionRequest(query);
|
||||
const gesture = GetGestureRequest(query);
|
||||
const dance = GetDanceRequest(query);
|
||||
const effect = GetEffectRequest(query);
|
||||
const frameNumber = GetFrameNumberRequest(query);
|
||||
const imageFormat = GetImageFormatRequest(query);
|
||||
|
||||
return {
|
||||
figure,
|
||||
size,
|
||||
setType,
|
||||
direction,
|
||||
headDirection,
|
||||
action,
|
||||
gesture,
|
||||
dance,
|
||||
effect,
|
||||
frameNumber,
|
||||
imageFormat
|
||||
} as IFigureBuildOptions;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { IFigureBuildOptions } from './IFigureBuildOptions';
|
||||
|
||||
const PART_SEPARATOR = '.';
|
||||
|
||||
export const BuildFigureOptionsStringRequest = (buildOptions: IFigureBuildOptions) =>
|
||||
{
|
||||
let buildString = '';
|
||||
|
||||
if(buildOptions.figure) buildString += buildOptions.figure;
|
||||
if(buildOptions.size) buildString += PART_SEPARATOR + 's-' + buildOptions.size;
|
||||
if(buildOptions.setType) buildString += PART_SEPARATOR + 'st-' + buildOptions.setType;
|
||||
if(buildOptions.direction) buildString += PART_SEPARATOR + 'd-' + buildOptions.direction;
|
||||
if(buildOptions.headDirection) buildString += PART_SEPARATOR + 'hd-' + buildOptions.headDirection;
|
||||
if(buildOptions.action) buildString += PART_SEPARATOR + 'a-' + buildOptions.action;
|
||||
if(buildOptions.gesture) buildString += PART_SEPARATOR + 'g-' + buildOptions.gesture;
|
||||
if(buildOptions.dance) buildString += PART_SEPARATOR + 'da-' + buildOptions.dance;
|
||||
if(buildOptions.effect) buildString += PART_SEPARATOR + 'fx-' + buildOptions.effect;
|
||||
if(buildOptions.frameNumber) buildString += PART_SEPARATOR + 'fn-' + buildOptions.frameNumber;
|
||||
if(buildOptions.imageFormat) buildString += PART_SEPARATOR + 'f-' + buildOptions.imageFormat;
|
||||
|
||||
return buildString;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetActionRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.action && query.action.length) ? query.action : null);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetDanceRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.dance && query.dance.length) ? parseInt(query.dance) : null);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetDirectionRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.direction && query.direction.length) ? parseInt(query.direction) : null);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetEffectRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.effect && query.effect.length) ? parseInt(query.effect) : null);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { AvatarRenderManager } from '../../../../avatar';
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetFigureRequest = (query: RequestQuery) =>
|
||||
{
|
||||
let figure = AvatarRenderManager.DEFAULT_FIGURE;
|
||||
|
||||
if(query.figure && query.figure.length) figure = query.figure;
|
||||
|
||||
return figure;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetFrameNumberRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.frame_num && query.frame_num.length) ? parseInt(query.frame_num) : -1);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetGestureRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.gesture && query.gesture.length) ? query.gesture : null);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetHeadDirectionRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return ((query.head_direction && query.head_direction.length) ? parseInt(query.head_direction) : null);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetImageFormatRequest = (query: RequestQuery) =>
|
||||
{
|
||||
if(query.img_format === 'gif') return 'gif';
|
||||
|
||||
return 'png';
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { AvatarScaleType } from '../../../../avatar';
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetScaleRequest = (query: RequestQuery) =>
|
||||
{
|
||||
return AvatarScaleType.LARGE;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { AvatarSetType } from '../../../../avatar';
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetSetTypeRequest = (query: RequestQuery) =>
|
||||
{
|
||||
let setType = AvatarSetType.FULL;
|
||||
|
||||
if(query.headonly && query.headonly == '1') setType = AvatarSetType.HEAD;
|
||||
|
||||
return setType;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { RequestQuery } from './RequestQuery';
|
||||
|
||||
export const GetSizeRequest = (query: RequestQuery) =>
|
||||
{
|
||||
if(query.size === 's') return 0.5;
|
||||
|
||||
if(query.size === 'l') return 2;
|
||||
|
||||
return 1;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
export interface IFigureBuildOptions
|
||||
{
|
||||
figure: string;
|
||||
size: number;
|
||||
setType: string;
|
||||
direction: number;
|
||||
headDirection: number;
|
||||
action: string;
|
||||
gesture: string;
|
||||
dance: number;
|
||||
effect: number;
|
||||
frameNumber: number;
|
||||
imageFormat: string;
|
||||
}
|
14
src/app/router/habbo-imaging/handlers/utils/RequestQuery.ts
Normal file
14
src/app/router/habbo-imaging/handlers/utils/RequestQuery.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export interface RequestQuery
|
||||
{
|
||||
figure: string;
|
||||
size: string;
|
||||
action: string;
|
||||
headonly: string;
|
||||
gesture: string;
|
||||
direction: string;
|
||||
head_direction: string;
|
||||
dance: string;
|
||||
effect: string;
|
||||
frame_num: string;
|
||||
img_format: string;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { IAvatarImage } from '../../../../../avatar';
|
||||
import { GetActionRequest } from '../GetActionRequest';
|
||||
import { RequestQuery } from '../RequestQuery';
|
||||
import { ProcessCarryAction } from './ProcessCarryAction';
|
||||
import { ProcessExpressionAction } from './ProcessExpressionAction';
|
||||
import { ProcessPostureAction } from './ProcessPostureAction';
|
||||
|
||||
export const ProcessActionRequest = (query: RequestQuery, avatar: IAvatarImage) =>
|
||||
{
|
||||
const actions = (GetActionRequest(query)?.split(',') || []);
|
||||
|
||||
for(const action of actions)
|
||||
{
|
||||
if(ProcessPostureAction(action, avatar)) continue;
|
||||
|
||||
if(ProcessExpressionAction(action, avatar)) continue;
|
||||
|
||||
if(ProcessCarryAction(action, avatar)) continue;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
|
||||
export const ProcessCarryAction = (action: string, avatar: IAvatarImage) =>
|
||||
{
|
||||
let didSet = false;
|
||||
|
||||
let carryType: string = null;
|
||||
let param: string = null;
|
||||
|
||||
if(action && action.length)
|
||||
{
|
||||
const [ key, value ] = action.split('=');
|
||||
|
||||
if(value && value.length) param = value;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case 'crr':
|
||||
case AvatarAction.CARRY_OBJECT:
|
||||
didSet = true;
|
||||
carryType = AvatarAction.CARRY_OBJECT;
|
||||
break;
|
||||
case 'drk':
|
||||
case AvatarAction.USE_OBJECT:
|
||||
didSet = true;
|
||||
carryType = AvatarAction.USE_OBJECT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(carryType && carryType.length && param && param.length) avatar.appendAction(carryType, param);
|
||||
|
||||
return didSet;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
|
||||
export const ProcessExpressionAction = (action: string, avatar: IAvatarImage) =>
|
||||
{
|
||||
let didSet = false;
|
||||
|
||||
let expression: string = null;
|
||||
let param: string = null;
|
||||
|
||||
if(action && action.length)
|
||||
{
|
||||
const [ key, value ] = action.split('=');
|
||||
|
||||
if(value && value.length) param = value;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case 'wav':
|
||||
case AvatarAction.EXPRESSION_WAVE:
|
||||
didSet = true;
|
||||
expression = AvatarAction.EXPRESSION_WAVE;
|
||||
break;
|
||||
case AvatarAction.EXPRESSION_BLOW_A_KISS:
|
||||
case AvatarAction.EXPRESSION_CRY:
|
||||
case AvatarAction.EXPRESSION_IDLE:
|
||||
case AvatarAction.EXPRESSION_LAUGH:
|
||||
case AvatarAction.EXPRESSION_RESPECT:
|
||||
case AvatarAction.EXPRESSION_RIDE_JUMP:
|
||||
case AvatarAction.EXPRESSION_SNOWBOARD_OLLIE:
|
||||
case AvatarAction.EXPRESSION_SNOWBORD_360:
|
||||
didSet = true;
|
||||
expression = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(expression && expression.length) avatar.appendAction(expression);
|
||||
|
||||
return didSet;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
|
||||
export const ProcessPostureAction = (action: string, avatar: IAvatarImage) =>
|
||||
{
|
||||
let didSet = false;
|
||||
|
||||
let posture = AvatarAction.POSTURE_STAND;
|
||||
let param = null;
|
||||
|
||||
if(action && action.length)
|
||||
{
|
||||
const [ key, value ] = action.split('=');
|
||||
|
||||
if(value && value.length) param = value;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case 'wlk':
|
||||
case AvatarAction.POSTURE_WALK:
|
||||
didSet = true;
|
||||
posture = AvatarAction.POSTURE_WALK;
|
||||
break;
|
||||
case AvatarAction.POSTURE_SIT:
|
||||
case AvatarAction.POSTURE_LAY:
|
||||
case AvatarAction.POSTURE_STAND:
|
||||
didSet = true;
|
||||
posture = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(posture && posture.length) avatar.appendAction(AvatarAction.POSTURE, posture, param);
|
||||
|
||||
return didSet;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export * from './ProcessActionRequest';
|
||||
export * from './ProcessCarryAction';
|
||||
export * from './ProcessExpressionAction';
|
||||
export * from './ProcessPostureAction';
|
@ -0,0 +1,20 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
import { GetDanceRequest } from '../GetDanceRequest';
|
||||
import { RequestQuery } from '../RequestQuery';
|
||||
|
||||
export const ProcessDanceRequest = (query: RequestQuery, avatar: IAvatarImage) =>
|
||||
{
|
||||
const dance: number = (GetDanceRequest(query) || null);
|
||||
|
||||
if(!dance) return;
|
||||
|
||||
switch(dance)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
avatar.appendAction(AvatarAction.DANCE, dance);
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './ProcessDanceRequest';
|
@ -0,0 +1,13 @@
|
||||
import { AvatarSetType, IAvatarImage } from '../../../../../avatar';
|
||||
import { GetDirectionRequest } from '../GetDirectionRequest';
|
||||
import { GetHeadDirectionRequest } from '../GetHeadDirectionRequest';
|
||||
import { RequestQuery } from '../RequestQuery';
|
||||
|
||||
export const ProcessDirectionRequest = (query: RequestQuery, avatar: IAvatarImage) =>
|
||||
{
|
||||
const direction = (GetDirectionRequest(query) || 2);
|
||||
const headDirection = (GetHeadDirectionRequest(query) || direction);
|
||||
|
||||
avatar.setDirection(AvatarSetType.FULL, direction);
|
||||
avatar.setDirection(AvatarSetType.HEAD, headDirection);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './ProcessDirectionRequest';
|
@ -0,0 +1,12 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
import { GetEffectRequest } from '../GetEffectRequest';
|
||||
import { RequestQuery } from '../RequestQuery';
|
||||
|
||||
export const ProcessEffectRequest = (query: RequestQuery, avatar: IAvatarImage) =>
|
||||
{
|
||||
const effect: number = (GetEffectRequest(query) || null);
|
||||
|
||||
if(!effect) return;
|
||||
|
||||
avatar.appendAction(AvatarAction.EFFECT, effect);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './ProcessEffectRequest';
|
@ -0,0 +1,27 @@
|
||||
import { AvatarAction, IAvatarImage } from '../../../../../avatar';
|
||||
import { GetGestureRequest } from '../GetGestureRequest';
|
||||
import { RequestQuery } from '../RequestQuery';
|
||||
|
||||
export const ProcessGestureRequest = (query: RequestQuery, avatar: IAvatarImage) =>
|
||||
{
|
||||
const gesture: string = (GetGestureRequest(query) || null);
|
||||
|
||||
if(!gesture) return;
|
||||
|
||||
switch(gesture)
|
||||
{
|
||||
case AvatarAction.POSTURE_STAND:
|
||||
case AvatarAction.GESTURE_AGGRAVATED:
|
||||
case AvatarAction.GESTURE_SAD:
|
||||
case AvatarAction.GESTURE_SMILE:
|
||||
case AvatarAction.GESTURE_SURPRISED:
|
||||
avatar.appendAction(AvatarAction.GESTURE, gesture);
|
||||
return;
|
||||
case 'spk':
|
||||
avatar.appendAction(AvatarAction.TALK);
|
||||
return;
|
||||
case 'eyb':
|
||||
avatar.appendAction(AvatarAction.SLEEP);
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './ProcessGestureRequest';
|
21
src/app/router/habbo-imaging/handlers/utils/index.ts
Normal file
21
src/app/router/habbo-imaging/handlers/utils/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export * from './action';
|
||||
export * from './BuildFigureOptionsRequest';
|
||||
export * from './BuildFigureOptionsStringRequest';
|
||||
export * from './dance';
|
||||
export * from './direction';
|
||||
export * from './effect';
|
||||
export * from './gesture';
|
||||
export * from './GetActionRequest';
|
||||
export * from './GetDanceRequest';
|
||||
export * from './GetDirectionRequest';
|
||||
export * from './GetEffectRequest';
|
||||
export * from './GetFigureRequest';
|
||||
export * from './GetFrameNumberRequest';
|
||||
export * from './GetGestureRequest';
|
||||
export * from './GetHeadDirectionRequest';
|
||||
export * from './GetImageFormatRequest';
|
||||
export * from './GetScaleRequest';
|
||||
export * from './GetSetTypeRequest';
|
||||
export * from './GetSizeRequest';
|
||||
export * from './IFigureBuildOptions';
|
||||
export * from './RequestQuery';
|
1
src/app/router/habbo-imaging/index.ts
Normal file
1
src/app/router/habbo-imaging/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './HabboImagingRouter';
|
@ -18,9 +18,17 @@ export class NitroCore extends NitroManager implements INitroCore
|
||||
|
||||
protected async onInit(): Promise<void>
|
||||
{
|
||||
if(this._configuration) await this._configuration.init();
|
||||
try
|
||||
{
|
||||
if(this._configuration) await this._configuration.init();
|
||||
|
||||
if(this._asset) await this._asset.init();
|
||||
if(this._asset) await this._asset.init();
|
||||
}
|
||||
|
||||
catch(err)
|
||||
{
|
||||
this.logger.error(err.message || err);
|
||||
}
|
||||
}
|
||||
|
||||
protected async onDispose(): Promise<void>
|
||||
|
@ -96,6 +96,7 @@ export class AssetManager extends NitroManager implements IAssetManager
|
||||
{
|
||||
try
|
||||
{
|
||||
this.logger.log('Downloading: ' + url);
|
||||
const buffer = await FileUtilities.readFileAsBuffer(url);
|
||||
const bundle = await NitroBundle.from(buffer);
|
||||
|
||||
|
@ -25,7 +25,17 @@ export class NitroManager extends Disposable implements INitroManager
|
||||
|
||||
this._isLoading = true;
|
||||
|
||||
await this.onInit();
|
||||
try
|
||||
{
|
||||
await this.onInit();
|
||||
}
|
||||
|
||||
catch(err)
|
||||
{
|
||||
this.logger.error(err.message || err);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._isLoaded = true;
|
||||
this._isLoading = false;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { NitroManager } from '../common';
|
||||
import { AdvancedMap } from '../utils';
|
||||
import { NitroManager } from '../common';
|
||||
import { AdvancedMap, FileUtilities } from '../utils';
|
||||
import { IConfigurationManager } from './IConfigurationManager';
|
||||
|
||||
export class ConfigurationManager extends NitroManager implements IConfigurationManager
|
||||
@ -16,17 +15,17 @@ export class ConfigurationManager extends NitroManager implements IConfiguration
|
||||
|
||||
protected async onInit(): Promise<void>
|
||||
{
|
||||
await this.loadConfigurationFromUrl((process.env.CONFIG_URL || null));
|
||||
await this.loadConfigurationFromUrl('./config.json');
|
||||
}
|
||||
|
||||
private async loadConfigurationFromUrl(url: string): Promise<void>
|
||||
{
|
||||
if(!url || (url === '')) return Promise.reject('invalid_config_url');
|
||||
if(!url || (url === '')) throw new Error(`Invalid configuration url: ${ url }`);
|
||||
|
||||
try
|
||||
{
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
const response = await FileUtilities.readFileAsString(url);
|
||||
const json = JSON.parse(response);
|
||||
|
||||
if(!this.parseConfiguration(json)) return Promise.reject('invalid_config');
|
||||
}
|
||||
|
@ -99,4 +99,25 @@ export class CanvasUtilities
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
public static scaleCanvas(canvas: Canvas, scaleX: number, scaleY: number): Canvas
|
||||
{
|
||||
const tempCanvas = this.createNitroCanvas((canvas.width * scaleX), (canvas.height * scaleY));
|
||||
const ctx = tempCanvas.getContext('2d');
|
||||
|
||||
ctx.scale(scaleX, scaleY);
|
||||
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
return tempCanvas;
|
||||
}
|
||||
|
||||
public static createNitroCanvas(width: number, height: number): Canvas
|
||||
{
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,6 @@ export class Point
|
||||
return new Point(this.x, this.y);
|
||||
}
|
||||
|
||||
public copyFrom(p: Point): Point
|
||||
{
|
||||
this.add(p.x, p.y);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public equals(p: Point): boolean
|
||||
{
|
||||
return ((p.x === this.x) && (p.y === this.y));
|
||||
@ -33,4 +26,14 @@ export class Point
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public add(point: Point): Point
|
||||
{
|
||||
const clone = this.clone();
|
||||
|
||||
clone.x += point.x;
|
||||
clone.y += point.y;
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
require('dotenv').config();
|
||||
|
||||
import { Application } from './app';
|
||||
import { NitroCore } from './core';
|
||||
|
||||
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": false,
|
||||
"removeComments": true,
|
||||
"noLib": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "es6",
|
||||
"sourceMap": false,
|
||||
"allowJs": false,
|
||||
"baseUrl": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"index.ts",
|
||||
"config.json"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user