diff --git a/itemImageGenerator.html b/itemImageGenerator.html
index 85ab492..bf655be 100644
--- a/itemImageGenerator.html
+++ b/itemImageGenerator.html
@@ -26,6 +26,7 @@
Sprite file:
Dat file:
Otb file:
+Only pickable items:
diff --git a/itemImageGenerator.ts b/itemImageGenerator.ts
index b8d9fef..b8e966c 100755
--- a/itemImageGenerator.ts
+++ b/itemImageGenerator.ts
@@ -13,6 +13,7 @@ class ItemImageGenerator {
private sprPicker: HTMLInputElement;
private datPicker: HTMLInputElement;
private otbPicker: HTMLInputElement;
+ private onlyPickupableCheckbox: HTMLInputElement;
private loadFilesButton: HTMLButtonElement;
private generateImagesButton: HTMLButtonElement;
@@ -22,17 +23,20 @@ class ItemImageGenerator {
private spriteManager: SpriteManager;
private datManager: DatManager;
private otbManager: OtbManager;
+ private onlyPickupable = true;
init() {
this.clientVersionInput = document.getElementById('clientversion');
this.sprPicker = document.getElementById('spr');
this.datPicker = document.getElementById('dat');
this.otbPicker = document.getElementById('otb');
+ this.onlyPickupableCheckbox = document.getElementById('onlyPickupable');
this.loadFilesButton = document.getElementById('loadFiles');
this.generateImagesButton = document.getElementById('generateImages');
const self = this;
this.loadFilesButton.onclick = function () {
+ self.onlyPickupable = self.onlyPickupableCheckbox.checked;
self.loadFiles();
};
this.generateImagesButton.onclick = function () {
@@ -42,7 +46,7 @@ class ItemImageGenerator {
}
const imageGenerator = new ImageGenerator(self.datManager, self.spriteManager, self.otbManager);
const zip = new JSZip();
- self.generateItemImage(imageGenerator, self.otbManager, self.datManager, zip, 0);
+ self.generateItemImage(imageGenerator, zip, 0);
};
}
@@ -127,10 +131,10 @@ class ItemImageGenerator {
}
}
- generateItemImage(imageGenerator: ImageGenerator, otbManager: OtbManager, datManager: DatManager, zip, serverId: number) {
+ generateItemImage(imageGenerator: ImageGenerator, zip, serverId: number) {
const self = this;
- this.progressValue(serverId, otbManager.getLastId());
- if (serverId > otbManager.getLastId()) {
+ this.progressValue(serverId, this.otbManager.getLastId());
+ if (serverId > this.otbManager.getLastId()) {
this.progressText('Packing images to ZIP file, please wait (it may take a while)');
zip.generateAsync({type: "blob"}).then(function (blob: Blob) {
console.log('zip size', blob.size);
@@ -140,33 +144,33 @@ class ItemImageGenerator {
return;
}
- if (!otbManager.isValidOtbId(serverId)) {
+ if (!this.otbManager.isValidOtbId(serverId)) {
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
return;
}
- const clientItemId = otbManager.getItem(serverId).getClientId();
+ const clientItemId = this.otbManager.getItem(serverId).getClientId();
if (!clientItemId) {
console.log('otb ID not mapped to any dat ID', serverId);
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
return;
}
- let itemThingType = datManager.getItem(clientItemId);
+ let itemThingType = this.datManager.getItem(clientItemId);
if (!itemThingType) {
console.log('dat ID not found in dat file', serverId, clientItemId);
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
return;
}
- if (!itemThingType.isPickupable()) {
+ if (this.onlyPickupable && !itemThingType.isPickupable()) {
console.log('skip not pickupable', serverId);
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
return;
}
@@ -174,7 +178,7 @@ class ItemImageGenerator {
const itemSprite = imageGenerator.generateItemImageByServerId(serverId);
if (!itemSprite) {
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
return;
}
@@ -193,7 +197,7 @@ class ItemImageGenerator {
canvas.remove();
zip.file(serverId + '.png', blob);
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
};
@@ -214,7 +218,7 @@ class ItemImageGenerator {
gif.on('finished', function (blob) {
zip.file(serverId + '.gif', blob);
setTimeout(function () {
- self.generateItemImage(imageGenerator, otbManager, datManager, zip, serverId + 1);
+ self.generateItemImage(imageGenerator, zip, serverId + 1);
}, 1);
});
diff --git a/modules/datFile/animator.ts b/modules/datFile/animator.ts
index d47918d..075cd4b 100644
--- a/modules/datFile/animator.ts
+++ b/modules/datFile/animator.ts
@@ -6,7 +6,7 @@ export class Animator {
m_startPhase = 0;
m_loopCount = 0;
m_async = false;
- m_phaseDurations = [];
+ m_phaseDurations: number[][] = [];
unserialize(animationPhases: number, fin: InputFile) {
this.m_animationPhases = animationPhases;
diff --git a/modules/datFile/datManager.ts b/modules/datFile/datManager.ts
index 9712917..4118bc9 100644
--- a/modules/datFile/datManager.ts
+++ b/modules/datFile/datManager.ts
@@ -122,7 +122,7 @@ export class DatManager {
firstId = 100;
for (let id = firstId; id < this.m_thingTypes[category].length; ++id)
- this.m_thingTypes[category][id].serialize(fin, this.m_client, clientTranslationArray);
+ this.m_thingTypes[category][id].serialize(fin, category, this.m_client, clientTranslationArray);
}
return fin;
}
diff --git a/modules/datFile/datThingType.ts b/modules/datFile/datThingType.ts
index 3d76f4f..f7642bf 100644
--- a/modules/datFile/datThingType.ts
+++ b/modules/datFile/datThingType.ts
@@ -10,30 +10,22 @@ import {Point} from "../structures/point";
import {MarketData} from "../structures/marketData";
import {Light} from "../structures/light";
import {OutputFile} from "../fileHandlers/outputFile";
+import {FrameGroup} from "./frameGroup";
export class DatThingType {
static maskColors = [Color.red, Color.green, Color.blue, Color.yellow];
- m_category: DatThingCategory;
- m_id: number = 0;
- m_null: boolean = true;
- m_attribs: DatThingTypeAttributes = new DatThingTypeAttributes();
-
- m_size: Size = new Size();
- m_displacement: Point = new Point();
- m_animator: Animator = null;
- m_animationPhases: number = 0;
- m_exactSize: number = 0;
- m_realSize: number = 0;
- m_numPatternX: number = 0;
- m_numPatternY: number = 0;
- m_numPatternZ: number = 0;
- m_layers: number = 0;
- m_elevation: number = 0;
-
- m_spritesIndex: number[] = [];
-
- serialize(fin: OutputFile, client: Client, clientAttributeTranslator) {
+ private m_category: DatThingCategory;
+ private m_id: number = 0;
+ private m_null: boolean = true;
+ private m_attribs: DatThingTypeAttributes = new DatThingTypeAttributes();
+
+ private m_displacement: Point = new Point();
+ private m_elevation: number = 0;
+
+ private m_frameGroups: FrameGroup[] = [];
+
+ serialize(fin: OutputFile, category: DatThingCategory, client: Client, clientAttributeTranslator) {
for (let clientAttrString in clientAttributeTranslator) {
if (clientAttributeTranslator.hasOwnProperty(clientAttrString)) {
let clientDatAttr = parseInt(clientAttrString);
@@ -44,9 +36,10 @@ export class DatThingType {
fin.addU8(clientDatAttr);
switch (thingAttr) {
case DatThingAttr.ThingAttrDisplacement: {
-
- fin.addU16(this.m_displacement.x);
- fin.addU16(this.m_displacement.y);
+ if (client.getClientVersion() >= 755) {
+ fin.addU16(this.m_displacement.x);
+ fin.addU16(this.m_displacement.y);
+ }
break;
}
case DatThingAttr.ThingAttrLight: {
@@ -82,29 +75,40 @@ export class DatThingType {
}
fin.addU8(DatThingAttr.ThingLastAttr);
- fin.addU8(this.m_size.width());
- fin.addU8(this.m_size.height());
+ let hasFrameGroups = (category == DatThingCategory.ThingCategoryCreature && client.getFeature(GameFeature.GameIdleAnimations));
+ if (hasFrameGroups)
+ fin.addU8(this.m_frameGroups.length);
+
+ for (let frameGroupType in this.m_frameGroups) {
+ if (hasFrameGroups)
+ fin.addU8(Number(frameGroupType));
- if (this.m_size.width() > 1 || this.m_size.height() > 1)
- fin.addU8(this.m_realSize);
+ const frameGroup = this.m_frameGroups[frameGroupType];
+ fin.addU8(frameGroup.m_size.width());
+ fin.addU8(frameGroup.m_size.height());
- fin.addU8(this.m_layers);
- fin.addU8(this.m_numPatternX);
- fin.addU8(this.m_numPatternY);
- fin.addU8(this.m_numPatternZ);
- fin.addU8(this.m_animationPhases);
+ if (frameGroup.m_size.width() > 1 || frameGroup.m_size.height() > 1)
+ fin.addU8(frameGroup.m_realSize);
- if (client.getFeature(GameFeature.GameEnhancedAnimations)) {
- if (this.m_animationPhases > 1 && this.m_animator != null) {
- this.m_animator.serialize(fin);
+ fin.addU8(frameGroup.m_layers);
+ fin.addU8(frameGroup.m_numPatternX);
+ fin.addU8(frameGroup.m_numPatternY);
+ if (client.getClientVersion() >= 755)
+ fin.addU8(frameGroup.m_numPatternZ);
+ fin.addU8(frameGroup.m_animationPhases);
+
+ if (client.getFeature(GameFeature.GameEnhancedAnimations)) {
+ if (frameGroup.m_animationPhases > 1 && frameGroup.m_animator != null) {
+ frameGroup.m_animator.serialize(fin);
+ }
}
- }
- for (let i2 = 0; i2 < this.m_spritesIndex.length; i2++) {
- if (client.getFeature(GameFeature.GameSpritesU32))
- fin.addU32(this.m_spritesIndex[i2]);
- else
- fin.addU16(this.m_spritesIndex[i2]);
+ for (let i2 = 0; i2 < frameGroup.m_spritesIndex.length; i2++) {
+ if (client.getFeature(GameFeature.GameSpritesU32))
+ fin.addU32(frameGroup.m_spritesIndex[i2]);
+ else
+ fin.addU16(frameGroup.m_spritesIndex[i2]);
+ }
}
}
@@ -182,89 +186,51 @@ export class DatThingType {
let hasFrameGroups = (category == DatThingCategory.ThingCategoryCreature && client.getFeature(GameFeature.GameIdleAnimations));
let groupCount = hasFrameGroups ? fin.getU8() : 1;
- this.m_animationPhases = 0;
- let totalSpritesCount = 0;
-
for (let i = 0; i < groupCount; ++i) {
- let frameGroupType = FrameGroupType.FrameGroupDefault;
+ let frameGroupType = (category == DatThingCategory.ThingCategoryCreature) ? FrameGroupType.FrameGroupMoving : FrameGroupType.FrameGroupIdle;
if (hasFrameGroups)
frameGroupType = fin.getU8();
- // TODO: load IDLE and MOVING frames
- if (groupCount == 1 || frameGroupType == FrameGroupType.FrameGroupMoving) {
- let width = fin.getU8();
- let height = fin.getU8();
- this.m_size = new Size(width, height);
- if (width > 1 || height > 1) {
- this.m_realSize = fin.getU8();
- this.m_exactSize = Math.min(this.m_realSize, Math.max(width * 32, height * 32));
- } else
- this.m_exactSize = 32;
-
- this.m_layers = fin.getU8();
- this.m_numPatternX = fin.getU8();
- this.m_numPatternY = fin.getU8();
- if (client.getClientVersion() >= 755)
- this.m_numPatternZ = fin.getU8();
- else
- this.m_numPatternZ = 1;
-
- let groupAnimationsPhases = fin.getU8();
- this.m_animationPhases = groupAnimationsPhases;
-
- if (groupAnimationsPhases > 1 && client.getFeature(GameFeature.GameEnhancedAnimations)) {
- this.m_animator = new Animator();
- this.m_animator.unserialize(groupAnimationsPhases, fin);
- }
-
- let totalSprites = this.m_size.area() * this.m_layers * this.m_numPatternX * this.m_numPatternY * this.m_numPatternZ * groupAnimationsPhases;
-
- if ((totalSpritesCount + totalSprites) > 4096)
- error("a thing type has more than 4096 sprites", totalSprites, totalSpritesCount, this.m_size.area(), this.m_layers, this.m_numPatternX, this.m_numPatternY, this.m_numPatternZ, groupAnimationsPhases);
-
- this.m_spritesIndex = [];
- for (let i = totalSpritesCount; i < (totalSpritesCount + totalSprites); i++) {
- this.m_spritesIndex[i] = client.getFeature(GameFeature.GameSpritesU32) ? fin.getU32() : fin.getU16();
- }
+ const frameGroup = new FrameGroup();
+
+ let width = fin.getU8();
+ let height = fin.getU8();
+ frameGroup.m_size = new Size(width, height);
+ if (width > 1 || height > 1) {
+ frameGroup.m_realSize = fin.getU8();
+ frameGroup.m_exactSize = Math.min(frameGroup.m_realSize, Math.max(width * 32, height * 32));
+ } else
+ frameGroup.m_exactSize = 32;
+
+ frameGroup.m_layers = fin.getU8();
+ frameGroup.m_numPatternX = fin.getU8();
+ frameGroup.m_numPatternY = fin.getU8();
+ if (client.getClientVersion() >= 755)
+ frameGroup.m_numPatternZ = fin.getU8();
+ else
+ frameGroup.m_numPatternZ = 1;
- //console.log('spr', this.m_spritesIndex);
- totalSpritesCount += totalSprites;
- } else {
- let width = fin.getU8();
- let height = fin.getU8();
- let tmpSize = new Size(width, height);
- let tmpExactSize = 32;
- if (width > 1 || height > 1) {
- let tmpRealSize = fin.getU8();
- tmpExactSize = Math.min(tmpRealSize, Math.max(width * 32, height * 32));
- }
+ let groupAnimationsPhases = fin.getU8();
+ frameGroup.m_animationPhases = groupAnimationsPhases;
- let tmpLayers = fin.getU8();
- let tmpNumPatternX = fin.getU8();
- let tmpNumPatternY = fin.getU8();
- let tmpNumPatternZ = 1;
- if (client.getClientVersion() >= 755)
- tmpNumPatternZ = fin.getU8();
+ if (groupAnimationsPhases > 1 && client.getFeature(GameFeature.GameEnhancedAnimations)) {
+ frameGroup.m_animator = new Animator();
+ frameGroup.m_animator.unserialize(groupAnimationsPhases, fin);
+ }
- let groupAnimationsPhases = fin.getU8();
+ let totalSprites = frameGroup.m_size.area() * frameGroup.m_layers * frameGroup.m_numPatternX * frameGroup.m_numPatternY * frameGroup.m_numPatternZ * groupAnimationsPhases;
- if (groupAnimationsPhases > 1 && client.getFeature(GameFeature.GameEnhancedAnimations)) {
- let tmpAnimator = new Animator();
- tmpAnimator.unserialize(groupAnimationsPhases, fin);
- }
+ if (totalSprites > 4096)
+ error("a thing type has more than 4096 sprites", totalSprites, frameGroup.m_size.area(), frameGroup.m_layers, frameGroup.m_numPatternX, frameGroup.m_numPatternY, frameGroup.m_numPatternZ, groupAnimationsPhases);
- let totalSprites = tmpSize.area() * tmpLayers * tmpNumPatternX * tmpNumPatternY * tmpNumPatternZ * groupAnimationsPhases;
+ frameGroup.m_spritesIndex = [];
+ for (let i = 0; i < totalSprites; i++) {
+ frameGroup.m_spritesIndex[i] = client.getFeature(GameFeature.GameSpritesU32) ? fin.getU32() : fin.getU16();
+ }
- if ((totalSpritesCount + totalSprites) > 4096)
- error("a thing type has more than 4096 sprites", totalSprites, totalSpritesCount, tmpSize.area(), tmpLayers, tmpNumPatternX, tmpNumPatternY, tmpNumPatternZ, groupAnimationsPhases);
+ //console.log('spr', this.m_spritesIndex);
- let tmpSpritesIndex = [];
- for (let i = totalSpritesCount; i < (totalSpritesCount + totalSprites); i++) {
- tmpSpritesIndex[i] = client.getFeature(GameFeature.GameSpritesU32) ? fin.getU32() : fin.getU16();
- }
-
- //console.log('spr', this.m_spritesIndex);
- }
+ this.m_frameGroups[frameGroupType] = frameGroup;
}
}
@@ -284,51 +250,6 @@ export class DatThingType {
return this.m_attribs.has(attr);
}
- getSize(): Size {
- return this.m_size;
- }
-
- getWidth(): number {
- return this.m_size.width();
- }
-
- getHeight(): number {
- return this.m_size.height();
- }
-
- getExactSize(layer: number = 0, xPattern: number = 0, yPattern: number = 0, zPattern: number = 0, animationPhase: number = 0): number {
- /* todo */
- return 0;
- }
-
- getRealSize(): number {
- return this.m_realSize;
- }
-
- getLayers(): number {
- return this.m_layers;
- }
-
- getNumPatternX(): number {
- return this.m_numPatternX;
- }
-
- getNumPatternY(): number {
- return this.m_numPatternY;
- }
-
- getNumPatternZ(): number {
- return this.m_numPatternZ;
- }
-
- getAnimationPhases(): number {
- return this.m_animationPhases;
- }
-
- getAnimator(): Animator {
- return this.m_animator;
- }
-
getDisplacement(): Point {
return this.m_displacement;
}
@@ -345,6 +266,14 @@ export class DatThingType {
return this.m_elevation;
}
+ getFrameGroups(): FrameGroup[] {
+ return this.m_frameGroups;
+ }
+
+ getFrameGroup(frameGroupType: FrameGroupType): FrameGroup {
+ return this.m_frameGroups[frameGroupType];
+ }
+
getGroundSpeed(): number {
return this.m_attribs.get(DatThingAttr.ThingAttrGround);
}
@@ -529,14 +458,6 @@ export class DatThingType {
return this.m_attribs.has(DatThingAttr.ThingAttrTopEffect);
}
- getSprites(): number[] {
- return this.m_spritesIndex;
- }
-
- getSprite(index: number): number {
- return this.m_spritesIndex[index];
- }
-
isNotPreWalkable(): boolean {
return this.m_attribs.has(DatThingAttr.ThingAttrNotPreWalkable);
}
@@ -578,121 +499,7 @@ export class DatThingType {
bestDimension = candidateDimension;
}
}
- //console.log('dim', this.m_id, bestDimension);
- return bestDimension;
- //return new Size(w, h);
- }
-
- getSpriteIndex(w: number, h: number, l: number, x: number, y: number, z: number, a: number): number {
- let index =
- ((((((a % this.m_animationPhases)
- * this.m_numPatternZ + z)
- * this.m_numPatternY + y)
- * this.m_numPatternX + x)
- * this.m_layers + l)
- * this.m_size.height() + h)
- * this.m_size.width() + w;
- if (!(index < this.m_spritesIndex.length)) {
- throw new Error('index < this.m_spritesIndex.length');
- }
- return index;
- }
- getTextureIndex(l: number, x: number, y: number, z: number) {
- return ((l * this.m_numPatternZ + z)
- * this.m_numPatternY + y)
- * this.m_numPatternX + x;
+ return bestDimension;
}
-
- /*
- getTexture(animationPhase: number): Texture {
-
- let animationPhaseTexture = this.m_textures[animationPhase];
- if (!animationPhaseTexture) {
-
- // we don't need layers in common items, they will be pre-drawn
- let textureLayers = 1;
- let numLayers = this.m_layers;
- if (this.m_category == ThingCategory.ThingCategoryCreature && numLayers >= 2) {
- // 5 layers: outfit base, red mask, green mask, blue mask, yellow mask
- textureLayers = 5;
- numLayers = 5;
- }
-
- let indexSize = textureLayers * this.m_numPatternX * this.m_numPatternY * this.m_numPatternZ;
- let textureSize = this.getBestTextureDimension(this.m_size.width(), this.m_size.height(), indexSize);
- //console.log('dim', textureSize, this);
- let fullImage = new Image(textureSize.mul(Otc.TILE_PIXELS));
-
- //console.log('fi', fullImage.getWidth(), fullImage.getHeight())
- this.m_texturesFramesRects[animationPhase] = [];
- this.m_texturesFramesOriginRects[animationPhase] = [];
- this.m_texturesFramesOffsets[animationPhase] = [];
-
- for (let z = 0; z < this.m_numPatternZ; ++z) {
- for (let y = 0; y < this.m_numPatternY; ++y) {
- for (let x = 0; x < this.m_numPatternX; ++x) {
- for (let l = 0; l < numLayers; ++l) {
- let spriteMask = (this.m_category == ThingCategory.ThingCategoryCreature && l > 0);
- let frameIndex = this.getTextureIndex(l % textureLayers, x, y, z);
- let framePos = new Point(toInt(frameIndex % toInt(textureSize.width() / this.m_size.width()) * this.m_size.width()) * Otc.TILE_PIXELS,
- toInt(frameIndex / toInt(textureSize.width() / this.m_size.width()) * this.m_size.height()) * Otc.TILE_PIXELS);
-
- //console.log('blitx', framePos);
- for (let h = 0; h < this.m_size.height(); ++h) {
- for (let w = 0; w < this.m_size.width(); ++w) {
- let spriteIndex = this.getSpriteIndex(w, h, spriteMask ? 1 : l, x, y, z, animationPhase);
- let spriteImage = g_sprites.getSpriteImage(this.m_spritesIndex[spriteIndex]);
-
- if (spriteImage) {
- if (spriteMask) {
- spriteImage.overwriteMask(ThingType.maskColors[l - 1]);
- }
- let spritePos = new Point((this.m_size.width() - w - 1) * Otc.TILE_PIXELS,
- (this.m_size.height() - h - 1) * Otc.TILE_PIXELS);
-
- fullImage.blit(framePos.add(spritePos), spriteImage);
- } else {
- //console.error(this.m_spritesIndex, spriteIndex);
- }
- }
- }
-
- let drawRect = new Rect(
- framePos.add(new Point(this.m_size.width(), this.m_size.height()))
- .mul(Otc.TILE_PIXELS)
- .sub(new Point(1, 1)),
- framePos);
-
- for (let x = framePos.x; x < framePos.x + this.m_size.width() * Otc.TILE_PIXELS; ++x) {
- for (let y = framePos.y; y < framePos.y + this.m_size.height() * Otc.TILE_PIXELS; ++y) {
-
- let p = fullImage.getPixel(x, y);
- if (p[3] != 0x00) {
- drawRect.setTop(Math.min(y, drawRect.top()));
- drawRect.setLeft(Math.min(x, drawRect.left()));
- drawRect.setBottom(Math.max(y, drawRect.bottom()));
- drawRect.setRight(Math.max(x, drawRect.right()));
- }
-
- }
- }
- //console.log('blit', drawRect);
-
- this.m_texturesFramesRects[animationPhase][frameIndex] = drawRect;
- this.m_texturesFramesOriginRects[animationPhase][frameIndex] = new Rect(framePos, new Size(this.m_size.width(), this.m_size.height()).mul(Otc.TILE_PIXELS));
- this.m_texturesFramesOffsets[animationPhase][frameIndex] = drawRect.topLeft().sub(framePos);
-
- }
- }
- }
- }
- animationPhaseTexture = new Texture(fullImage, true);
- //animationPhaseTexture.setSmooth(true);
- //console.log(this.m_id, animationPhase, animationPhaseTexture);
- this.m_textures[animationPhase] = animationPhaseTexture;
- }
- return animationPhaseTexture;
- }
- */
}
diff --git a/modules/datFile/frameGroup.ts b/modules/datFile/frameGroup.ts
new file mode 100644
index 0000000..cfb4798
--- /dev/null
+++ b/modules/datFile/frameGroup.ts
@@ -0,0 +1,87 @@
+import {InputFile} from "../fileHandlers/inputFile";
+import {OutputFile} from "../fileHandlers/outputFile";
+import {Animator} from "./animator";
+import {Size} from "../structures/size";
+
+export class FrameGroup {
+ m_size: Size = new Size();
+ m_animator: Animator = null;
+ m_animationPhases: number = 0;
+ m_exactSize: number = 0;
+ m_realSize: number = 0;
+ m_numPatternX: number = 0;
+ m_numPatternY: number = 0;
+ m_numPatternZ: number = 0;
+ m_layers: number = 0;
+ m_spritesIndex: number[] = [];
+
+ getSize(): Size {
+ return this.m_size;
+ }
+
+ getWidth(): number {
+ return this.m_size.width();
+ }
+
+ getHeight(): number {
+ return this.m_size.height();
+ }
+
+ getRealSize(): number {
+ return this.m_realSize;
+ }
+
+ getLayers(): number {
+ return this.m_layers;
+ }
+
+ getNumPatternX(): number {
+ return this.m_numPatternX;
+ }
+
+ getNumPatternY(): number {
+ return this.m_numPatternY;
+ }
+
+ getNumPatternZ(): number {
+ return this.m_numPatternZ;
+ }
+
+ getAnimationPhases(): number {
+ return this.m_animationPhases;
+ }
+
+ getAnimator(): Animator {
+ return this.m_animator;
+ }
+
+ getSprites(): number[] {
+ return this.m_spritesIndex;
+ }
+
+ getSprite(index: number): number {
+ return this.m_spritesIndex[index];
+ }
+
+ getSpriteIndex(w: number, h: number, l: number, x: number, y: number, z: number, a: number): number {
+ let index =
+ ((((((a % this.m_animationPhases)
+ * this.m_numPatternZ + z)
+ * this.m_numPatternY + y)
+ * this.m_numPatternX + x)
+ * this.m_layers + l)
+ * this.m_size.height() + h)
+ * this.m_size.width() + w;
+ if (!(index < this.m_spritesIndex.length)) {
+ throw new Error('index < this.m_spritesIndex.length');
+ }
+ return index;
+ }
+
+ getTextureIndex(l: number, x: number, y: number, z: number) {
+ return ((l * this.m_numPatternZ + z)
+ * this.m_numPatternY + y)
+ * this.m_numPatternX + x;
+ }
+
+}
diff --git a/modules/imageGenerator/imageGenerator.ts b/modules/imageGenerator/imageGenerator.ts
index 8b12416..f10308c 100644
--- a/modules/imageGenerator/imageGenerator.ts
+++ b/modules/imageGenerator/imageGenerator.ts
@@ -4,6 +4,7 @@ import {OtbManager} from "../otbFile/otbManager";
import {Sprite} from "../sprFile/sprite";
import {Size} from "../structures/size";
import {Point} from "../structures/point";
+import {FrameGroupType} from "../constants/const";
export class ImageGenerator {
constructor(private datManager: DatManager = null, private sprManager: SpriteManager = null, private otbManager: OtbManager = null) {
@@ -52,13 +53,19 @@ export class ImageGenerator {
return null;
}
- const itemSprite = new Sprite(new Size(SpriteManager.SPRITE_SIZE * itemThingType.m_size.width(), SpriteManager.SPRITE_SIZE * itemThingType.m_size.height()));
+ const frameGroup = itemThingType.getFrameGroup(FrameGroupType.FrameGroupIdle);
+ if (!frameGroup) {
+ console.log('missing idle frameGroup item', clientItemId);
+ return null;
+ }
+
+ const itemSprite = new Sprite(new Size(SpriteManager.SPRITE_SIZE * frameGroup.m_size.width(), SpriteManager.SPRITE_SIZE * frameGroup.m_size.height()));
- for (let l = 0; l < itemThingType.m_layers; ++l) {
- for (let w = 0; w < itemThingType.m_size.width(); ++w) {
- for (let h = 0; h < itemThingType.m_size.height(); ++h) {
- const spriteId = itemThingType.m_spritesIndex[
- itemThingType.getSpriteIndex(w, h, l, 0, 0, 0, animationFrame)
+ for (let l = 0; l < frameGroup.m_layers; ++l) {
+ for (let w = 0; w < frameGroup.m_size.width(); ++w) {
+ for (let h = 0; h < frameGroup.m_size.height(); ++h) {
+ const spriteId = frameGroup.m_spritesIndex[
+ frameGroup.getSpriteIndex(w, h, l, 0, 0, 0, animationFrame)
];
const sprite = this.sprManager.getSprite(spriteId);
if (!sprite) {
@@ -69,8 +76,8 @@ export class ImageGenerator {
}
itemSprite.blit(
new Point(
- SpriteManager.SPRITE_SIZE * (itemThingType.m_size.width() - w - 1),
- SpriteManager.SPRITE_SIZE * (itemThingType.m_size.height() - h - 1)
+ SpriteManager.SPRITE_SIZE * (frameGroup.m_size.width() - w - 1),
+ SpriteManager.SPRITE_SIZE * (frameGroup.m_size.height() - h - 1)
),
sprite
);
@@ -94,8 +101,14 @@ export class ImageGenerator {
return null;
}
+ const frameGroup = itemThingType.getFrameGroup(FrameGroupType.FrameGroupIdle);
+ if (!frameGroup) {
+ console.log('missing idle frameGroup item', clientItemId);
+ return null;
+ }
+
const itemSprites = [];
- for (let a = 0; a < itemThingType.m_animationPhases; ++a) {
+ for (let a = 0; a < frameGroup.m_animationPhases; ++a) {
const itemSprite = this.generateItemImageByClientId(clientItemId, a);
if (itemSprite) {
itemSprites.push(itemSprite);
@@ -105,7 +118,7 @@ export class ImageGenerator {
return itemSprites;
}
- generateOutfitAnimationImages(outfitId: number) {
+ generateOutfitAnimationImages(outfitId: number, frameGroupType: FrameGroupType = FrameGroupType.FrameGroupMoving) {
if (this.datManager === null) {
throw new Error("datManager is not set");
}
@@ -118,19 +131,25 @@ export class ImageGenerator {
return null;
}
+ const frameGroup = outfitThingType.getFrameGroup(frameGroupType);
+ if (!frameGroup) {
+ console.log('missing frameGroup outfit', outfitId, frameGroupType);
+ return null;
+ }
+
const sprites = [];
- for(let z = 0; z < outfitThingType.m_numPatternZ; ++z) {
- for(let y = 0; y < outfitThingType.m_numPatternY; ++y) {
- for(let x = 0; x < outfitThingType.m_numPatternX; ++x) {
- for(let l = 0; l < outfitThingType.m_layers; ++l) {
- for(let a = 0; a < outfitThingType.m_animationPhases; ++a) {
+ for(let z = 0; z < frameGroup.m_numPatternZ; ++z) {
+ for(let y = 0; y < frameGroup.m_numPatternY; ++y) {
+ for(let x = 0; x < frameGroup.m_numPatternX; ++x) {
+ for(let l = 0; l < frameGroup.m_layers; ++l) {
+ for(let a = 0; a < frameGroup.m_animationPhases; ++a) {
console.log('generate', 'outfits_anim/' + outfitId + '/' + (a+1) + '/' + (z+1) + '/' + (y+1) + '/' +(x+1))
- const outfitSprite = new Sprite(new Size(SpriteManager.SPRITE_SIZE * outfitThingType.m_size.width(), SpriteManager.SPRITE_SIZE * outfitThingType.m_size.height()));
- for(let w = 0; w < outfitThingType.m_size.width(); ++w) {
- for(let h = 0; h < outfitThingType.m_size.height(); ++h) {
- const spriteId = outfitThingType.m_spritesIndex[
- outfitThingType.getSpriteIndex(w, h, l, x, y, z, a)
+ const outfitSprite = new Sprite(new Size(SpriteManager.SPRITE_SIZE * frameGroup.m_size.width(), SpriteManager.SPRITE_SIZE * frameGroup.m_size.height()));
+ for(let w = 0; w < frameGroup.m_size.width(); ++w) {
+ for(let h = 0; h < frameGroup.m_size.height(); ++h) {
+ const spriteId = frameGroup.m_spritesIndex[
+ frameGroup.getSpriteIndex(w, h, l, x, y, z, a)
];
const sprite = this.sprManager.getSprite(spriteId);
if (!sprite) {
@@ -141,8 +160,8 @@ export class ImageGenerator {
}
outfitSprite.blit(
new Point(
- SpriteManager.SPRITE_SIZE * (outfitThingType.m_size.width() - w - 1),
- SpriteManager.SPRITE_SIZE * (outfitThingType.m_size.height() - h - 1)
+ SpriteManager.SPRITE_SIZE * (frameGroup.m_size.width() - w - 1),
+ SpriteManager.SPRITE_SIZE * (frameGroup.m_size.height() - h - 1)
),
sprite
);
diff --git a/outfitImageGenerator.html b/outfitImageGenerator.html
index 386c99f..3e8f2eb 100644
--- a/outfitImageGenerator.html
+++ b/outfitImageGenerator.html
@@ -26,6 +26,7 @@
Sprite file:
Dat file:
Otb file:
+Load idle animation when possible:
diff --git a/outfitImageGenerator.ts b/outfitImageGenerator.ts
index 20116a4..278724c 100644
--- a/outfitImageGenerator.ts
+++ b/outfitImageGenerator.ts
@@ -4,7 +4,7 @@ import {OtbManager} from "./modules/otbFile/otbManager";
import {SpriteManager} from "./modules/sprFile/spriteManager";
import {InputFile} from "./modules/fileHandlers/inputFile";
import {ImageGenerator} from "./modules/imageGenerator/imageGenerator";
-import {DatThingCategory} from "./modules/constants/const";
+import {DatThingCategory, FrameGroupType} from "./modules/constants/const";
let JSZip = require('jszip');
let GIF = require('gif.js');
@@ -14,6 +14,7 @@ class OutfitImageGenerator {
private sprPicker: HTMLInputElement;
private datPicker: HTMLInputElement;
private otbPicker: HTMLInputElement;
+ private idleAnimation: HTMLInputElement;
private loadFilesButton: HTMLButtonElement;
private generateImagesButton: HTMLButtonElement;
@@ -23,17 +24,20 @@ class OutfitImageGenerator {
private spriteManager: SpriteManager;
private datManager: DatManager;
private otbManager: OtbManager;
+ private tryLoadIdleAnimation = true;
init() {
this.clientVersionInput = document.getElementById('clientversion');
this.sprPicker = document.getElementById('spr');
this.datPicker = document.getElementById('dat');
this.otbPicker = document.getElementById('otb');
+ this.idleAnimation = document.getElementById('idleAnimation');
this.loadFilesButton = document.getElementById('loadFiles');
this.generateImagesButton = document.getElementById('generateImages');
const self = this;
this.loadFilesButton.onclick = function () {
+ self.tryLoadIdleAnimation = self.idleAnimation.checked;
self.loadFiles();
};
this.generateImagesButton.onclick = function () {
@@ -141,7 +145,13 @@ class OutfitImageGenerator {
return;
}
- const outfitSprites = imageGenerator.generateOutfitAnimationImages(outfitId);
+ let outfitSprites;
+ if (this.tryLoadIdleAnimation) {
+ outfitSprites = imageGenerator.generateOutfitAnimationImages(outfitId, FrameGroupType.FrameGroupIdle);
+ }
+ if (!outfitSprites || outfitSprites.length == 0) {
+ outfitSprites = imageGenerator.generateOutfitAnimationImages(outfitId, FrameGroupType.FrameGroupMoving);
+ }
if (!outfitSprites || outfitSprites.length == 0) {
setTimeout(function () {
self.generateOutfitImage(imageGenerator, otbManager, datManager, zip, outfitId + 1);
diff --git a/package-lock.json b/package-lock.json
index e77a39d..2baff28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "open-tibia-library",
- "version": "0.0.2",
+ "version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 39b1f61..9d9fa84 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "open-tibia-library",
- "version": "0.0.2",
+ "version": "0.1.0",
"description": "OpenTibiaLibrary",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
diff --git a/tests.ts b/tests.ts
index e86b7fa..e420c87 100755
--- a/tests.ts
+++ b/tests.ts
@@ -7,6 +7,7 @@ import {InputFile} from "./modules/fileHandlers/inputFile";
import {ImageGenerator} from "./modules/imageGenerator/imageGenerator";
import {Size} from "./modules/structures/size";
import {Point} from "./modules/structures/point";
+import {FrameGroupType} from "./modules/constants/const";
const canvas = document.getElementById('view');
const ctx = canvas.getContext("2d");
@@ -19,9 +20,9 @@ function drawImage(sprite: Sprite, x, y) {
async function testGenerateAllItemImages() {
const client = new Client();
- client.setClientVersion(860);
+ client.setClientVersion(1076);
- const serverUrl = 'http://127.0.0.1/Tibia860/';
+ const serverUrl = 'http://127.0.0.1/1076/';
const datManager = new DatManager(client);
await datManager.loadDatFromUrl(serverUrl + 'Tibia.dat').then(datLoaded => {
@@ -42,9 +43,9 @@ async function testGenerateAllItemImages() {
async function testLoadFromUrlsAndDrawImage() {
const client = new Client();
- client.setClientVersion(860);
+ client.setClientVersion(1076);
- const serverUrl = 'http://127.0.0.1/Tibia860/';
+ const serverUrl = 'http://127.0.0.1/1076/';
const datManager = new DatManager(client);
await datManager.loadDatFromUrl(serverUrl + 'Tibia.dat').then(datLoaded => {
@@ -66,7 +67,7 @@ async function testLoadFromUrlsAndDrawImage() {
// get data from '.dat' about that item
let magicSwordThingType = datManager.getItem(magicSwordClientId);
// get first sprite [image] of that item
- let firstMagicSwordSprite = magicSwordThingType.getSprite(0);
+ let firstMagicSwordSprite = magicSwordThingType.getFrameGroup(FrameGroupType.FrameGroupIdle).getSprite(0);
// get image from .spr file
let firstImagePixelsData = spriteManager.getSprite(firstMagicSwordSprite);
// draw image in webbrowser with Canvas on position 0, 0
@@ -89,6 +90,16 @@ async function testLoadFromUrlsAndDrawImage() {
spriteManager: spriteManager
};
+
+ // let otbFile = otbManager.saveOtb();
+ // let a = document.createElement('a');
+ // let url = window.URL.createObjectURL(new Blob(new Array(otbFile.getUint8Array())));
+ // a.href = url;
+ // a.download = 'items.otb';
+ // a.click();
+ // window.URL.revokeObjectURL(url);
+ // a.remove();
+
// console.log('Generated dat file', datManager.saveDat());
// console.log('Generated otb file', otbManager.saveOtb());
// console.log('Generated spr file', spriteManager.saveSpr());
@@ -117,7 +128,7 @@ async function testFilePicker() {
try {
let magicSwordClientId = otbManager.getItem(itemid).getClientId();
let magicSwordThingType = datManager.getItem(magicSwordClientId);
- let firstMagicSwordSprite = magicSwordThingType.getSprite(0);
+ let firstMagicSwordSprite = magicSwordThingType.getFrameGroup(FrameGroupType.FrameGroupIdle).getSprite(0);
let firstImagePixelsData = sprManager.getSprite(firstMagicSwordSprite);
drawImage(firstImagePixelsData, 0, 0);
@@ -136,9 +147,6 @@ async function testFilePicker() {
otbPicker.onchange(null);
updateItemView(parseInt(itemIdInput.value));
};
- itemIdInput.onchange = function (event) {
- updateItemView(parseInt(itemIdInput.value));
- };
sprPicker.onchange = function (event) {
if (sprPicker.files.length > 0) {
@@ -178,10 +186,14 @@ async function testFilePicker() {
}
}
};
+
+ itemIdInput.onchange = function (event) {
+ updateItemView(parseInt(itemIdInput.value));
+ };
}
-testGenerateAllItemImages();
-// testLoadFromUrlsAndDrawImage();
+// testGenerateAllItemImages();
+testLoadFromUrlsAndDrawImage();
// testFilePicker();
/*
download OTB:
@@ -194,4 +206,4 @@ a.download = 'items.otb';
a.click();
window.URL.revokeObjectURL(url);
a.remove();
- */
\ No newline at end of file
+ */
diff --git a/tools/colored-outfit-images-generator/animoutfit.php b/tools/colored-outfit-images-generator/animoutfit.php
index 3ca3ad2..17f57b3 100644
--- a/tools/colored-outfit-images-generator/animoutfit.php
+++ b/tools/colored-outfit-images-generator/animoutfit.php
@@ -31,7 +31,7 @@
// Block sites that hotlink your host and overload it
$abusersList = array('aurera-global.com', 'bad-server.com');
-if (in_array(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), $abusersList) || in_array(substr(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), 4), $abusersList)) {
+if (isset($_SERVER['HTTP_REFERER']) && (in_array(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), $abusersList) || in_array(substr(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), 4), $abusersList))) {
header('Content-Type: image/png');
readfile('abuse_warning.png');
exit;
diff --git a/tools/colored-outfit-images-generator/libs/outfitter.php b/tools/colored-outfit-images-generator/libs/outfitter.php
index 2ca5eb3..75f5e0e 100644
--- a/tools/colored-outfit-images-generator/libs/outfitter.php
+++ b/tools/colored-outfit-images-generator/libs/outfitter.php
@@ -73,10 +73,12 @@ public static function loadData($outfitId, $isMount = false)
{
$tmp = unserialize(file_get_contents(self::$outfitPath . $outfitId . '/outfit.data.txt'));
self::$data['files'] = array_merge(self::$data['files'], $tmp['files']);
+ self::$data['mountFramesNumber'] = $tmp['framesNumber'];
}
else
{
self::$data = unserialize(file_get_contents(self::$outfitPath . $outfitId . '/outfit.data.txt'));
+ self::$data['mountFramesNumber'] = 1;
}
return true;
}
@@ -90,7 +92,7 @@ public static function getOutfitFramesNumber()
public static function file_exists($filePath)
{
- return in_array(str_replace('\\', '/', $filePath), self::$data['files']);
+ return in_array(trim(trim(str_replace('\\', '/', $filePath), '.'), '/'), self::$data['files']);
}
public function outfit($outfit, $addons, $head, $body, $legs, $feet, $mount, $direction = 3, $animation = 1) {
@@ -111,33 +113,19 @@ public function outfit($outfit, $addons, $head, $body, $legs, $feet, $mount, $di
$mountId = $mount;
$mountState = 2;
}
- $creature = false;
- if ($creature) {
- $tmpOutfit = null;
- if (self::file_exists(self::$outfitPath . $outfit . '/'.$animation.'_1_1_'.$direction.'.png'))
- $tmpOutfit = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_1_1_'.$direction.'.png');
- elseif (self::file_exists(self::$outfitPath . $outfit . '/1_1_1_3.png'))
- $tmpOutfit = imagecreatefrompng(self::$outfitPath . $outfit . '/1_1_1_3.png');
- if ($tmpOutfit == null)
- return false;
-
- $width = imagesx($tmpOutfit);
- $height = imagesy($tmpOutfit);
- $image_outfit = imagecreatetruecolor($width, $height);
- imagefill($image_outfit, 0, 0, $bgcolor = imagecolorallocate($image_outfit, self::$transparentBackgroundColor[0], self::$transparentBackgroundColor[1], self::$transparentBackgroundColor[2]));
- imagecopyresampled($image_outfit, $tmpOutfit, 0, 0, 0, 0, $width, $height, $width, $height);
- imagecolortransparent($image_outfit, $bgcolor);
+ $image_outfit = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_1_'.$direction.'.png');
+ if (file_exists(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_1_'.$direction.'_template.png')) {
+ $image_template = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_1_'.$direction.'_template.png');
+ } else {
+ $image_template = imagecreatetruecolor(imagesx($image_outfit), imagesy($image_outfit));
+ $bgcolor = imagecolorallocate($image_template, self::$transparentBackgroundColor[0], self::$transparentBackgroundColor[1], self::$transparentBackgroundColor[2]);
+ imagecolortransparent($image_template, $bgcolor);
- imagealphablending($image_outfit, false);
- imagesavealpha($image_outfit, true);
- imagedestroy($tmpOutfit);
- return $image_outfit;
+ imagealphablending($image_template, false);
+ imagesavealpha($image_template, true);
}
- $image_outfit = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_1_'.$direction.'.png');
- $image_template = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_1_'.$direction.'_template.png');
-
if ($addons == 1 || $addons == 3) {
$image_first = imagecreatefrompng(self::$outfitPath . $outfit . '/'.$animation.'_' . $mountState . '_2_'.$direction.'.png');
$this->alphaOverlay($image_outfit, $image_first, 64, 64);
@@ -162,8 +150,10 @@ public function outfit($outfit, $addons, $head, $body, $legs, $feet, $mount, $di
}
$this->colorize($image_template, $image_outfit, $head, $body, $legs, $feet);
- if ($mountState == 2 && self::file_exists(self::$outfitPath . $mountId . '/'.$animation.'_1_1_'.$direction.'.png')) {
- $mount = imagecreatefrompng(self::$outfitPath . $mountId . '/'.$animation.'_1_1_'.$direction.'.png');
+ $mountAnimationFrame = max(1,(self::$data['mountFramesNumber'] % $animation));
+
+ if ($mountState == 2 && self::file_exists(self::$outfitPath . $mountId . '/'.$mountAnimationFrame.'_1_1_'.$direction.'.png')) {
+ $mount = imagecreatefrompng(self::$outfitPath . $mountId . '/'.$mountAnimationFrame.'_1_1_'.$direction.'.png');
$this->alphaOverlay($mount, $image_outfit, 64, 64);
imagedestroy($image_outfit);
$image_outfit = $mount;
@@ -179,7 +169,9 @@ public function outfit($outfit, $addons, $head, $body, $legs, $feet, $mount, $di
imagealphablending($image_outfitT, false);
imagesavealpha($image_outfitT, true);
imagedestroy($image_outfit);
- imagedestroy($image_template);
+ if (isset($image_template)) {
+ imagedestroy($image_template);
+ }
return $image_outfitT;
}
diff --git a/tools/colored-outfit-images-generator/outfit.php b/tools/colored-outfit-images-generator/outfit.php
index 684c0b1..fb61258 100644
--- a/tools/colored-outfit-images-generator/outfit.php
+++ b/tools/colored-outfit-images-generator/outfit.php
@@ -18,8 +18,7 @@
// Block sites that hotlink your host and overload it
$abusersList = array('aurera-global.com', 'bad-server.com');
-if(in_array(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), $abusersList) || in_array(substr(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), 4), $abusersList))
-{
+if (isset($_SERVER['HTTP_REFERER']) && (in_array(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), $abusersList) || in_array(substr(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST), 4), $abusersList))) {
header('Content-Type: image/png');
readfile('abuse_warning.png');
exit;
@@ -71,4 +70,4 @@
$animationFrame = 1;
header('Content-type: image/gif');
-imagegif(Outfitter::instance()->outfit($_GET['id'], $_GET['addons'], $_GET['head'], $_GET['body'], $_GET['legs'], $_GET['feet'], $mount, $direction, $animationFrame));
+imagegif(Outfitter::instance()->outfit($id, $addons, $head, $body, $legs, $feet, $mount, $direction, $animationFrame));