tibia-rme/source/graphics.cpp
dalkon 886cbc23a7 Lots of changes
Removed libxml2 to use pugixml.
Fixed a bug with the progress bar.
Changed some things in most xml files that pugixml did not like.
Updated the Visual Studio project.
Any NULL symbol has been changed to nullptr.
This most probably broke the CMake that was recently added.
And probably some other things.
2014-01-22 20:16:17 +01:00

2023 lines
51 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
// $URL: http://svn.rebarp.se/svn/RME/trunk/source/graphics.hpp $
// $Id: graphics.hpp 310 2010-02-26 18:03:48Z admin $
#include "main.h"
#include "sprites.h"
#include "graphics.h"
#include "filehandle.h"
#include "settings.h"
#include "gui.h"
#include <wx/mstream.h>
#include "pngfiles.h"
// All 133 template colors
static uint32_t TemplateOutfitLookupTable[] = {
0xFFFFFF, 0xFFD4BF, 0xFFE9BF, 0xFFFFBF, 0xE9FFBF, 0xD4FFBF,
0xBFFFBF, 0xBFFFD4, 0xBFFFE9, 0xBFFFFF, 0xBFE9FF, 0xBFD4FF,
0xBFBFFF, 0xD4BFFF, 0xE9BFFF, 0xFFBFFF, 0xFFBFE9, 0xFFBFD4,
0xFFBFBF, 0xDADADA, 0xBF9F8F, 0xBFAF8F, 0xBFBF8F, 0xAFBF8F,
0x9FBF8F, 0x8FBF8F, 0x8FBF9F, 0x8FBFAF, 0x8FBFBF, 0x8FAFBF,
0x8F9FBF, 0x8F8FBF, 0x9F8FBF, 0xAF8FBF, 0xBF8FBF, 0xBF8FAF,
0xBF8F9F, 0xBF8F8F, 0xB6B6B6, 0xBF7F5F, 0xBFAF8F, 0xBFBF5F,
0x9FBF5F, 0x7FBF5F, 0x5FBF5F, 0x5FBF7F, 0x5FBF9F, 0x5FBFBF,
0x5F9FBF, 0x5F7FBF, 0x5F5FBF, 0x7F5FBF, 0x9F5FBF, 0xBF5FBF,
0xBF5F9F, 0xBF5F7F, 0xBF5F5F, 0x919191, 0xBF6A3F, 0xBF943F,
0xBFBF3F, 0x94BF3F, 0x6ABF3F, 0x3FBF3F, 0x3FBF6A, 0x3FBF94,
0x3FBFBF, 0x3F94BF, 0x3F6ABF, 0x3F3FBF, 0x6A3FBF, 0x943FBF,
0xBF3FBF, 0xBF3F94, 0xBF3F6A, 0xBF3F3F, 0x6D6D6D, 0xFF5500,
0xFFAA00, 0xFFFF00, 0xAAFF00, 0x54FF00, 0x00FF00, 0x00FF54,
0x00FFAA, 0x00FFFF, 0x00A9FF, 0x0055FF, 0x0000FF, 0x5500FF,
0xA900FF, 0xFE00FF, 0xFF00AA, 0xFF0055, 0xFF0000, 0x484848,
0xBF3F00, 0xBF7F00, 0xBFBF00, 0x7FBF00, 0x3FBF00, 0x00BF00,
0x00BF3F, 0x00BF7F, 0x00BFBF, 0x007FBF, 0x003FBF, 0x0000BF,
0x3F00BF, 0x7F00BF, 0xBF00BF, 0xBF007F, 0xBF003F, 0xBF0000,
0x242424, 0x7F2A00, 0x7F5500, 0x7F7F00, 0x557F00, 0x2A7F00,
0x007F00, 0x007F2A, 0x007F55, 0x007F7F, 0x00547F, 0x002A7F,
0x00007F, 0x2A007F, 0x54007F, 0x7F007F, 0x7F0055, 0x7F002A,
0x7F0000,
};
GraphicManager::GraphicManager() :
client_version(nullptr),
unloaded(true),
datVersion(DAT_VERSION_UNKNOWN),
sprVersion(SPR_VERSION_UNKNOWN),
loaded_textures(0),
lastclean(0)
{
// ...
}
GraphicManager::~GraphicManager()
{
for(SpriteMap::iterator iter = sprite_space.begin();
iter != sprite_space.end();
++iter)
{
delete iter->second;
}
for(ImageMap::iterator iter = image_space.begin();
iter != image_space.end();
++iter)
{
delete iter->second;
}
}
bool GraphicManager::isUnloaded() const
{
return unloaded;
}
GLuint GraphicManager::getFreeTextureID()
{
static GLuint id_counter = 0x10000000;
return id_counter++; // This should (hopefully) never run out
}
void GraphicManager::clear()
{
SpriteMap new_sprite_space;
for(SpriteMap::iterator iter = sprite_space.begin();
iter != sprite_space.end();
++iter)
{
if(iter->first >= 0) { // Don't clean internal sprites
delete iter->second;
} else {
new_sprite_space.insert(std::make_pair(iter->first, iter->second));
}
}
for(ImageMap::iterator iter = image_space.begin();
iter != image_space.end();
++iter)
{
delete iter->second;
}
sprite_space.swap(new_sprite_space);
image_space.clear();
cleanup_list.clear();
item_count = 0;
creature_count = 0;
loaded_textures = 0;
lastclean = time(nullptr);
spritefile = "";
unloaded = true;
}
void GraphicManager::cleanSoftwareSprites()
{
for(SpriteMap::iterator iter = sprite_space.begin();
iter != sprite_space.end();
++iter)
{
if(iter->first >= 0) { // Don't clean internal sprites
iter->second->unloadDC();
}
}
}
Sprite* GraphicManager::getSprite(int id)
{
SpriteMap::iterator it = sprite_space.find(id);
if(it != sprite_space.end())
{
return it->second;
}
return nullptr;
}
GameSprite* GraphicManager::getCreatureSprite(int id)
{
if(id < 0)
{
return nullptr;
}
SpriteMap::iterator it = sprite_space.find(id+item_count);
if(it != sprite_space.end())
{
return static_cast<GameSprite*>(it->second);
}
return nullptr;
}
uint16_t GraphicManager::getItemSpriteMaxID() const
{
return item_count;
}
uint16_t GraphicManager::getCreatureSpriteMaxID() const
{
return creature_count;
}
#define loadPNGFile(name) _wxGetBitmapFromMemory(name, sizeof(name))
inline wxBitmap* _wxGetBitmapFromMemory(const unsigned char* data, int length)
{
wxMemoryInputStream is(data, length);
wxImage img(is, wxT("image/png"));
if(!img.IsOk()) return nullptr;
return newd wxBitmap(img, -1);
}
bool GraphicManager::loadEditorSprites()
{
// Unused graphics MIGHT be loaded here, but it's a neglectable loss
sprite_space[EDITOR_SPRITE_SELECTION_MARKER] =
newd EditorSprite(
newd wxBitmap(selection_marker_xpm16x16),
newd wxBitmap(selection_marker_xpm32x32)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_1x1] =
newd EditorSprite(
loadPNGFile(circular_1_small_png),
loadPNGFile(circular_1_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_3x3] =
newd EditorSprite(
loadPNGFile(circular_2_small_png),
loadPNGFile(circular_2_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_5x5] =
newd EditorSprite(
loadPNGFile(circular_3_small_png),
loadPNGFile(circular_3_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_7x7] =
newd EditorSprite(
loadPNGFile(circular_4_small_png),
loadPNGFile(circular_4_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_9x9] =
newd EditorSprite(
loadPNGFile(circular_5_small_png),
loadPNGFile(circular_5_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_15x15] =
newd EditorSprite(
loadPNGFile(circular_6_small_png),
loadPNGFile(circular_6_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_CD_19x19] =
newd EditorSprite(
loadPNGFile(circular_7_small_png),
loadPNGFile(circular_7_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_1x1] =
newd EditorSprite(
loadPNGFile(rectangular_1_small_png),
loadPNGFile(rectangular_1_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_3x3] =
newd EditorSprite(
loadPNGFile(rectangular_2_small_png),
loadPNGFile(rectangular_2_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_5x5] =
newd EditorSprite(
loadPNGFile(rectangular_3_small_png),
loadPNGFile(rectangular_3_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_7x7] =
newd EditorSprite(
loadPNGFile(rectangular_4_small_png),
loadPNGFile(rectangular_4_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_9x9] =
newd EditorSprite(
loadPNGFile(rectangular_5_small_png),
loadPNGFile(rectangular_5_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_15x15] =
newd EditorSprite(
loadPNGFile(rectangular_6_small_png),
loadPNGFile(rectangular_6_png)
);
sprite_space[EDITOR_SPRITE_BRUSH_SD_19x19] =
newd EditorSprite(
loadPNGFile(rectangular_7_small_png),
loadPNGFile(rectangular_7_png)
);
sprite_space[EDITOR_SPRITE_OPTIONAL_BORDER_TOOL] =
newd EditorSprite(
loadPNGFile(optional_border_small_png),
loadPNGFile(optional_border_png)
);
sprite_space[EDITOR_SPRITE_ERASER] =
newd EditorSprite(
loadPNGFile(eraser_small_png),
loadPNGFile(eraser_png)
);
sprite_space[EDITOR_SPRITE_PZ_TOOL] =
newd EditorSprite(
loadPNGFile(protection_zone_small_png),
loadPNGFile(protection_zone_png)
);
sprite_space[EDITOR_SPRITE_PVPZ_TOOL] =
newd EditorSprite(
loadPNGFile(pvp_zone_small_png),
loadPNGFile(pvp_zone_png)
);
sprite_space[EDITOR_SPRITE_NOLOG_TOOL] =
newd EditorSprite(
loadPNGFile(no_logout_small_png),
loadPNGFile(no_logout_png)
);
sprite_space[EDITOR_SPRITE_NOPVP_TOOL] =
newd EditorSprite(
loadPNGFile(no_pvp_small_png),
loadPNGFile(no_pvp_png)
);
sprite_space[EDITOR_SPRITE_DOOR_NORMAL] =
newd EditorSprite(
loadPNGFile(door_normal_small_png),
loadPNGFile(door_normal_png)
);
sprite_space[EDITOR_SPRITE_DOOR_LOCKED] =
newd EditorSprite(
loadPNGFile(door_locked_small_png),
loadPNGFile(door_locked_png)
);
sprite_space[EDITOR_SPRITE_DOOR_MAGIC] =
newd EditorSprite(
loadPNGFile(door_magic_small_png),
loadPNGFile(door_magic_png)
);
sprite_space[EDITOR_SPRITE_DOOR_QUEST] =
newd EditorSprite(
loadPNGFile(door_quest_small_png),
loadPNGFile(door_quest_png)
);
sprite_space[EDITOR_SPRITE_WINDOW_NORMAL] =
newd EditorSprite(
loadPNGFile(window_normal_small_png),
loadPNGFile(window_normal_png)
);
sprite_space[EDITOR_SPRITE_WINDOW_HATCH] =
newd EditorSprite(
loadPNGFile(window_hatch_small_png),
loadPNGFile(window_hatch_png)
);
sprite_space[EDITOR_SPRITE_SELECTION_GEM] =
newd EditorSprite(
loadPNGFile(gem_edit_png),
nullptr
);
sprite_space[EDITOR_SPRITE_DRAWING_GEM] =
newd EditorSprite(
loadPNGFile(gem_move_png),
nullptr
);
return true;
}
bool GraphicManager::loadSpriteMetadata(const FileName& datafile, wxString& error, wxArrayString& warnings)
{
// items.otb has most of the info we need. This only loads the GameSprite metadata
FileReadHandle file(nstr(datafile.GetFullPath()));
if(file.isOk() == false) {
error += wxT("Failed to open ") + datafile.GetFullPath() + wxT(" for reading\nThe error reported was:") + wxstr(file.getErrorMessage());
return false;
}
uint16_t effect_count, distance_count;
uint32_t datSignature;
file.getU32(datSignature);
//get max id
file.getU16(item_count);
file.getU16(creature_count);
file.getU16(effect_count);
file.getU16(distance_count);
uint minclientID = 100; // tibia.dat start with id 100
// We don't load distance/effects, if we would, just add effect_count & distance_count here
uint maxclientID = item_count + creature_count;
bool (GraphicManager::*loadFlags)(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings);
datVersion = client_version->getDatVersionForSignature(datSignature);
switch (datVersion)
{
case DAT_VERSION_74: loadFlags = &GraphicManager::loadSpriteMetadataFlagsVer74; break;
case DAT_VERSION_76: loadFlags = &GraphicManager::loadSpriteMetadataFlagsVer76; break;
case DAT_VERSION_78: loadFlags = &GraphicManager::loadSpriteMetadataFlagsVer78; break;
case DAT_VERSION_86: loadFlags = &GraphicManager::loadSpriteMetadataFlagsVer86; break;
// Same .dat loader, the change is only sprite id -> u32
case DAT_VERSION_96: loadFlags = &GraphicManager::loadSpriteMetadataFlagsVer86; break;
default:
error = wxT("Unknown .dat format version.");
return false;
}
uint16_t id = minclientID;
// loop through all ItemDatabase until we reach the end of file
while(id <= maxclientID)
{
GameSprite* sType = newd GameSprite();
sprite_space[id] = sType;
sType->id = id;
// Load the sprite flags
if (!(this->*loadFlags)(file, sType, error, warnings))
{
wxString msg;
msg << wxT("Failed to load flags for sprite ") << sType->id;
warnings.push_back(msg);
}
// Size and GameSprite data
file.getByte(sType->width);
file.getByte(sType->height);
if((sType->width > 1) || (sType->height > 1)){
file.skip(1); // No idea what this is....
}
file.getU8(sType->frames); // Number of blendframes (some sprites consist of several merged sprites)
file.getU8(sType->xdiv);
file.getU8(sType->ydiv);
if(datVersion == DAT_VERSION_74)
sType->zdiv = 1;
else
file.getU8(sType->zdiv); // Is this ever used? Maybe it means something else?
file.getU8(sType->animation_length); // Length of animation
sType->numsprites =
(int)sType->width * (int)sType->height *
(int)sType->frames *
(int)sType->xdiv * (int)sType->ydiv * sType->zdiv *
(int)sType->animation_length;
// Read the sprite ids
for(uint i = 0; i < sType->numsprites; ++i)
{
uint32_t sprite_id;
if (datVersion == DAT_VERSION_96)
{
file.getU32(sprite_id);
}
else
{
uint16_t u16 = 0;
file.getU16(u16);
sprite_id = u16;
}
if(image_space[sprite_id] == nullptr)
{
GameSprite::NormalImage* img = newd GameSprite::NormalImage();
img->id = sprite_id;
image_space[sprite_id] = img;
}
sType->spriteList.push_back(static_cast<GameSprite::NormalImage*>(image_space[sprite_id]));
}
++id;
}
return true;
}
bool GraphicManager::loadSpriteMetadataFlagsVer74(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings)
{
// read the options until we find a 0xff
uint8_t lastopt;
uint8_t optbyte = 0xff;
do
{
lastopt = optbyte;
file.getByte(optbyte);
switch(optbyte)
{
case 0x00:
//is groundtile
file.skip(2);
break;
case 0x01: // all OnTop
break;
case 0x02: // can walk trough (open doors, arces, bug pen fence ??)
break;
case 0x03:
//is a container
break;
case 0x04:
//is stackable
break;
case 0x05:
//is useable
break;
case 0x06: // ladder up (id 1386) why a group for just 1 item ???
break;
case 0x07: // writtable objects
file.skip(2);
break;
case 0x08: // writtable objects that can't be edited
file.skip(2);
break;
case 0x09: //can contain fluids
break;
case 0x0A:
//is multitype !!! wrong definition (only water splash on floor)
break;
case 0x0B:
//is blocking
break;
case 0x0C:
//is on moveable
break;
case 0x0D: // blocks missiles (walls, magic wall etc)
//iType->blockingProjectile = true;
break;
case 0x0E: // blocks monster movement (flowers, parcels etc)
break;
case 0x0F:
//can be equipped
break;
case 0x10:
//makes light (skip 4 bytes)
file.skip(4);
break;
case 0x11: // can see what is under (ladder holes, stairs holes etc)
break;
case 0x12: // ground tiles that don't cause level change
//iType->noFloorChange = true;
break;
case 0x13: // mostly blocking items, but also items that can pile up in level (boxes, chairs etc)
file.skip(2);
break;
case 0x14: // player color templates
break;
case 0x18: // cropses that don't decay
break;
case 0x16: // ground, blocking items and mayby some more
file.skip(2);
break;
case 0x17: // seems like decorables with 4 states of turning (exception first 4 are unique statues)
break;
case 0x19: // wall items
break;
case 0x1A:
//7.4 (change no data ?? ) action that can be performed (doors-> open, hole->open, book->read) not all included ex. wall torches
break;
case 0x1B: // walls 2 types of them same material (total 4 pairs)
break;
case 0x1C: // ??
break;
case 0x1D: // line spot ...
file.skip(2);
break;
case 0xFF:
break;
default:
{
wxString err;
err << wxT("Tibia.dat: Unknown optbyte '")
<< optbyte << wxT("'") << wxT(" after '")
<< lastopt << wxT("'");
warnings.push_back(err);
break;
}
}
} while (optbyte != 0xff);
return true;
}
bool GraphicManager::loadSpriteMetadataFlagsVer76(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings)
{
// read the options until we find a 0xff
uint8_t lastopt;
uint8_t optbyte = 0xff;
do
{
lastopt = optbyte;
file.getByte(optbyte);
switch (optbyte)
{
case 0x00: //is groundtile
{
file.skip(2);
break;
}
case 0x01: //all on top
{
//sType->alwaysOnTop = true;
break;
}
case 0x02: //can walk trough (open doors, arces, bug pen fence ??)
{
//sType->alwaysOnTop = true;
//sType->canWalkThrough = true;
break;
}
case 0x03: //is a container
{
//sType->group = ITEM_GROUP_CONTAINER;
break;
}
case 0x04: //is stackable
{
//sType->stackable = true;
break;
}
case 0x05: //is useable
{
//sType->useable = true;
break;
}
case 0x06: //ladder up (id 1386) why a group for just 1 item ???
{
//sType->ladderUp = true;
break;
}
case 0x07: //writtable objects
{
//file.skip(2); // Maximum length
break;
}
case 0x08: //writtable objects that can't be edited
{
file.skip(2);
break;
}
case 0x09: //can contain fluids
{
file.skip(2);
//sType->group = ITEM_GROUP_FLUID;
break;
}
case 0x0A: //liquid with states
{
//sType->group = ITEM_GROUP_SPLASH;
break;
}
case 0x0B: //is blocking
{
//sType->blockSolid = true;
break;
}
case 0x0C: //is not moveable
{
//sType->moveable = false;
break;
}
case 0x0D: //"visibility"- for drawing visible view
{
//sType->blockProjectile = true;
break;
}
case 0x0E: //blocks creature movement (flowers, parcels etc)
{
//sType->blockPathFind = true;
break;
}
case 0x0F: //can be equipped
{
//sType->pickupable = true;
break;
}
case 0x10: //makes light (skip 4 bytes)
{
break;
}
case 0x11: //can see what is under (ladder holes, stairs holes etc)
{
//sType->canSeeThrough = true;
break;
}
case 0x12: //ground tiles that don't cause level change
{
//sType->floorchange = false;
break;
}
case 0x13: // isVertical
{
break;
}
case 0x14: //sprite-drawing related
{
//sType->hasParameter14 = true;
break;
}
case 0x15:
{
file.skip(2); // lightlevel
file.skip(2); // lightcolor
break;
}
case 0x16: //minimap drawing
{
uint16_t u16;
file.getU16(u16);
sType->minimap_color = u16;
break;
}
case 0x17: //seems like decorables with 4 states of turning (exception first 4 are unique statues
{
//sType->rotable = true;
break;
}
case 0x18: //draw with height offset for all parts (2x2) of the sprite
{
file.skip(4);
break;
}
case 0x19: //hangable objects
{
file.skip(2);
break;
}
case 0x1A: //vertical objects (walls to hang objects on etc)
{
//sType->isHorizontal = true;
break;
}
case 0x1B: //walls 2 types of them same material (total 4 pairs)
{
//sType->isVertical = true;
break;
}
case 0x1C: // minimap color
{
uint16_t u16;
file.getU16(u16);
sType->minimap_color = u16; // Height
break;
}
case 0x1D: //line spot ...
{
file.skip(2); // ...?
break;
}
case 0x1E: // ground items
{
break;
}
case 0xFF:
{
break;
}
default:
{
wxString err;
err << wxT("Tibia.dat: Unknown optbyte '")
<< (int)optbyte << wxT("'") << wxT(" after '")
<< (int)lastopt << wxT("'");
warnings.push_back(err);
break;
}
}
} while (optbyte != 0xff);
return true;
}
bool GraphicManager::loadSpriteMetadataFlagsVer78(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings)
{
// read the options until we find a 0xff
uint8_t lastopt;
uint8_t optbyte = 0xff;
do
{
lastopt = optbyte;
file.getByte(optbyte);
// > 7.6
switch(optbyte)
{
case 0x00: //is groundtile
file.skip(2); // speed modifier
//speed = read_short;
//sType->speed = speed;
//sType->group = ITEM_GROUP_GROUND;
break;
case 0x01: //all OnTop
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 1;
break;
case 0x02: //can walk through
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 2;
break;
case 0x03: //can walk through
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 3;
break;
case 0x04: //is a container
//sType->group = ITEM_GROUP_CONTAINER;
break;
case 0x05: //is stackable
//sType->stackable = true;
break;
case 0x06: //ladders
break;
case 0x07: //is useable
//sType->useable = true;
break;
case 0x08: //runes
//sType->group = ITEM_GROUP_RUNE;
break;
case 0x09: //writeable objects
//sType->group = ITEM_GROUP_WRITEABLE;
//sType->readable = true;
file.skip(2);
//file.getU16(); //maximum text length?
break;
case 0x0A: //writeable objects that can't be edited
//sType->readable = true;
file.skip(2);
//file.getU16(); //maximum text length?
break;
case 0x0B: //can contain fluids
//sType->group = ITEM_GROUP_FLUID;
break;
case 0x0C: //liquid with states
//sType->group = ITEM_GROUP_SPLASH;
break;
case 0x0D: //is blocking
//sType->blockSolid = true;
break;
case 0x0E: //is not moveable
//sType->moveable = false;
break;
case 0x0F: //blocks missiles (walls, magic wall etc)
//sType->blockProjectile = true;
break;
case 0x10: //blocks monster movement (flowers, parcels etc)
//sType->blockPathFind = true;
break;
case 0x11: //can be equipped
//sType->pickupable = true;
break;
case 0x12: //wall items
//sType->isHangable = true;
break;
case 0x13:
//sType->isHorizontal = true;
break;
case 0x14:
//sType->isVertical = true;
break;
case 0x15: //rotateable items
//sType->rotateable = true;
break;
case 0x16: //light info ..
file.skip(2);
file.skip(2);
//file.getU16(); // level
//file.getU16(); // color
//sType->lightColor = lightcolor;
break;
case 0x17: // No object has this...
break;
case 0x18: //floor change down
break;
case 0x19: { //Draw offset
uint16_t x;
uint16_t y;
file.getU16(x);
file.getU16(y);
sType->drawoffset_x = x;
sType->drawoffset_y = y;
} break;
case 0x1A: {
uint16_t u16;
file.getU16(u16);
sType->draw_height = u16; // Height
} break;
case 0x1B://draw with height offset for all parts (2x2) of the sprite
break;
case 0x1C: // offset life-bar (for larger monsters)
break;
case 0x1D: { // Minimap color
uint16_t u16;
file.getU16(u16);
sType->minimap_color = u16;
} break;
case 0x1E: {// Floor change?
file.skip(2);
//file.getByte(); // 86 -> openable holes, 77-> can be used to go down, 76 can be used to go up, 82 -> stairs up, 79 switch,
//file.getByte(); // always 4
} break;
case 0x1F:
break;
case 0x20: // LookThrough
break;
case 0xFF:
break;
default: {
wxString err;
err << wxT("Tibia.dat: Unknown optbyte '")
<< (int)optbyte << wxT("'") << wxT(" after '")
<< (int)lastopt << wxT("'");
warnings.push_back(err);
return false;
} break;
}
} while (optbyte != 0xff);
return true;
}
bool GraphicManager::loadSpriteMetadataFlagsVer86(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings)
{
// read the options until we find a 0xff
uint8_t lastopt;
uint8_t optbyte = 0xff;
do
{
lastopt = optbyte;
file.getByte(optbyte);
switch(optbyte)
{
case 0x00: //is groundtile
file.skip(2); // speed modifier
//speed = read_short;
//sType->speed = speed;
//sType->group = ITEM_GROUP_GROUND;
break;
case 0x01: //all OnTop
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 1;
break;
case 0x02: //can walk through
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 2;
break;
case 0x03: //can walk through
//sType->alwaysOnTop = true;
//sType->alwaysOnTopOrder = 3;
break;
case 0x04: //is a container
//sType->group = ITEM_GROUP_CONTAINER;
break;
case 0x05: //is stackable
//sType->stackable = true;
break;
case 0x06: //ladders
break;
case 0x07: //is useable
//sType->useable = true;
break;
case 0x08: //writeable objects
//sType->group = ITEM_GROUP_WRITEABLE;
//sType->readable = true;
file.skip(2);
//file.getU16(); //maximum text length?
break;
case 0x09: //writeable objects that can't be edited
//sType->readable = true;
file.skip(2);
//file.getU16(); //maximum text length?
break;
case 0x0A: //can contain fluids
//sType->group = ITEM_GROUP_FLUID;
break;
case 0x0B: //liquid with states
//sType->group = ITEM_GROUP_SPLASH;
break;
case 0x0C: //is blocking
//sType->blockSolid = true;
break;
case 0x0D: //is not moveable
//sType->moveable = false;
break;
case 0x0E: //blocks missiles (walls, magic wall etc)
//sType->blockProjectile = true;
break;
case 0x0F: //blocks monster movement (flowers, parcels etc)
//sType->blockPathFind = true;
break;
case 0x10: //can be equipped
//sType->pickupable = true;
break;
case 0x11: //wall items
//sType->isHangable = true;
break;
case 0x12:
//sType->isHorizontal = true;
break;
case 0x13:
//sType->isVertical = true;
break;
case 0x14: //rotateable items
//sType->rotateable = true;
break;
case 0x15: //light info ..
file.skip(2);
file.skip(2);
//file.getU16(); // level
//file.getU16(); // color
//sType->lightColor = lightcolor;
break;
case 0x16: // No object has this...
break;
case 0x17: //floor change down
break;
case 0x18: { //Draw offset
uint16_t x;
uint16_t y;
file.getU16(x);
file.getU16(y);
sType->drawoffset_x = x;
sType->drawoffset_y = y;
} break;
case 0x19: {
uint16_t u16;
file.getU16(u16);
sType->draw_height = u16; // Height
} break;
case 0x1A://draw with height offset for all parts (2x2) of the sprite
break;
case 0x1B: // offset life-bar (for larger monsters)
break;
case 0x1C: { // Minimap color
uint16_t u16;
file.getU16(u16);
sType->minimap_color = u16;
} break;
case 0x1D: {// Floor change?
file.skip(2);
//file.getByte(); // 86 -> openable holes, 77-> can be used to go down, 76 can be used to go up, 82 -> stairs up, 79 switch,
//file.getByte(); // always 4
} break;
case 0x1E:
break;
case 0x1F: // LookThrough
break;
// All stuff below is hacky speculation
case 0x20: // Cloth slot
file.skip(2); // clothSlot
break;
case 0x21: // Market
{
/* marketCategory = */ file.skip(2);
/* marketTradeAs = */ file.skip(2);
/* marketShowAs = */ file.skip(2);
std::string marketName;
file.getString(marketName);
/* marketRestrictProfession = */ file.skip(2);
/* marketRestrictLevel = */ file.skip(2);
break;
}
case 0xFF:
break;
default: {
wxString err;
err << wxT("Tibia.dat: Unknown optbyte '")
<< (int)optbyte << wxT("'") << wxT(" after '")
<< (int)lastopt << wxT("'");
warnings.push_back(err);
return false;
} break;
}
} while (optbyte != 0xff);
return true;
}
bool GraphicManager::loadSpriteData(const FileName& datafile, wxString& error, wxArrayString& warnings)
{
FileReadHandle fh(nstr(datafile.GetFullPath()));
if(fh.isOk() == false) {
error = wxT("Failed to open file for reading");
return false;
}
#define safe_get(func, ...) do {\
if(!fh.get##func(__VA_ARGS__)) {\
error = wxstr(fh.getErrorMessage()); \
return false; \
} \
} while(false)
uint32_t sprSignature;
safe_get(U32, sprSignature);
uint32_t total_pics = 0;
sprVersion = client_version->getSprVersionForSignature(sprSignature);
switch (sprVersion)
{
case SPR_VERSION_70:
{
uint16_t u16 = 0;
safe_get(U16, u16);
total_pics = u16;
break;
}
case SPR_VERSION_96:
safe_get(U32, total_pics);
break;
default:
error = wxT("Unrecognized spr signature");
return false;
}
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES) == false) {
spritefile = nstr(datafile.GetFullPath());
unloaded = false;
return true;
}
std::vector<uint> sprite_indexes;
for(uint i = 0; i < total_pics; ++i) {
uint32_t index;
safe_get(U32, index);
sprite_indexes.push_back(index);
}
// Now read individual sprites
int id = 1;
for(std::vector<uint>::iterator sprite_iter = sprite_indexes.begin();
sprite_iter != sprite_indexes.end();
++sprite_iter, ++id)
{
uint index = *sprite_iter + 3;
fh.seek(index);
uint16_t size;
safe_get(U16, size);
ImageMap::iterator it = image_space.find(id);
if(it != image_space.end()) {
GameSprite::NormalImage* spr = dynamic_cast<GameSprite::NormalImage*>(it->second);
if(spr && size > 0) {
if(spr->size > 0) {
wxString ss;
ss << wxT("items.spr: Duplicate GameSprite id ") << id;
warnings.push_back(ss);
fh.seekRelative(size);
} else {
spr->id = id;
spr->size = size;
spr->dump = newd uint8_t[size];
if(!fh.getRAW(spr->dump, size)) {
error = wxstr(fh.getErrorMessage()); \
return false;
}
}
}
} else {
fh.seekRelative(size);
}
}
#undef safe_get
unloaded = false;
return true;
}
bool GraphicManager::loadSpriteDump(uint8_t*& target, uint16_t& size, int sprite_id)
{
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES))
return false;
if(sprite_id == 0)
{
// Empty GameSprite
size = 0;
target = nullptr;
return true;
}
FileReadHandle fh(spritefile);
if(fh.isOk() == false)
return false;
unloaded = false;
switch (sprVersion)
{
case SPR_VERSION_70:
// Sprite ids start at 1, so signature is implicitly skipped
// Then skip the 2 byte total_pics
if(!fh.seek(2 + sprite_id * sizeof(uint32_t)))
return false;
break;
case SPR_VERSION_96:
// Sprite ids start at 1, so signature is implicitly skipped
// Then skip the 4 byte total_pics
if(!fh.seek(4 + sprite_id * sizeof(uint32_t)))
return false;
break;
default:
return false;
}
uint32_t to_seek = 0;
if(fh.getU32(to_seek))
{
fh.seek(to_seek+3);
uint16_t sprite_size;
if(fh.getU16(sprite_size))
{
target = newd uint8_t[sprite_size];
if(fh.getRAW(target, sprite_size))
{
size = sprite_size;
return true;
}
delete[] target;
target = nullptr;
}
}
return false;
}
void GraphicManager::addSpriteToCleanup(GameSprite* spr)
{
cleanup_list.push_back(spr);
// Clean if needed
if(cleanup_list.size() > uint(max(100, settings.getInteger(Config::SOFTWARE_CLEAN_THRESHOLD))))
{
for(int i = 0; i < settings.getInteger(Config::SOFTWARE_CLEAN_SIZE) && uint(i) < cleanup_list.size(); ++i)
{
cleanup_list.front()->unloadDC();
cleanup_list.pop_front();
}
}
}
void GraphicManager::garbageCollection()
{
if(settings.getInteger(Config::TEXTURE_MANAGEMENT))
{
int t = time(nullptr);
if(loaded_textures > settings.getInteger(Config::TEXTURE_CLEAN_THRESHOLD) &&
t - lastclean > settings.getInteger(Config::TEXTURE_CLEAN_PULSE))
{
ImageMap::iterator iit = image_space.begin();
while(iit != image_space.end())
{
iit->second->clean(t);
++iit;
}
SpriteMap::iterator sit = sprite_space.begin();
while(sit != sprite_space.end())
{
GameSprite* gs = dynamic_cast<GameSprite*>(sit->second);
if(gs) gs->clean(t);
++sit;
}
lastclean = t;
}
}
}
EditorSprite::EditorSprite(wxBitmap* b16x16, wxBitmap* b32x32)
{
bm[SPRITE_SIZE_16x16] = b16x16;
bm[SPRITE_SIZE_32x32] = b32x32;
}
EditorSprite::~EditorSprite()
{
unloadDC();
}
void EditorSprite::DrawTo(wxDC* dc, SpriteSize sz, int start_x, int start_y, int width, int height)
{
wxBitmap* sp = bm[sz];
if(sp)
dc->DrawBitmap(*sp, start_x, start_y, true);
}
void EditorSprite::unloadDC()
{
delete bm[SPRITE_SIZE_16x16];
delete bm[SPRITE_SIZE_32x32];
bm[SPRITE_SIZE_16x16] = nullptr;
bm[SPRITE_SIZE_32x32] = nullptr;
}
GameSprite::GameSprite() :
id(0),
height(0),
width(0),
frames(0),
xdiv(0),
ydiv(0),
zdiv(0),
animation_length(0),
numsprites(0),
draw_height(0),
drawoffset_x(0),
drawoffset_y(0),
minimap_color(0)
{
dc[SPRITE_SIZE_16x16] = nullptr;
dc[SPRITE_SIZE_32x32] = nullptr;
}
GameSprite::~GameSprite() {
unloadDC();
for(std::list<TemplateImage*>::iterator iter = instanced_templates.begin();
iter != instanced_templates.end();
++iter)
{
delete *iter;
}
}
void GameSprite::clean(int time) {
for(std::list<TemplateImage*>::iterator iter = instanced_templates.begin();
iter != instanced_templates.end();
++iter)
{
(*iter)->clean(time);
}
}
void GameSprite::unloadDC() {
delete dc[SPRITE_SIZE_16x16];
delete dc[SPRITE_SIZE_32x32];
dc[SPRITE_SIZE_16x16] = nullptr;
dc[SPRITE_SIZE_32x32] = nullptr;
}
int GameSprite::getDrawHeight() const {
return draw_height;
}
std::pair<int, int> GameSprite::getDrawOffset() const {
return std::make_pair(drawoffset_x, drawoffset_y);
}
uint8_t GameSprite::getMiniMapColor() const {
return minimap_color;
}
GLuint GameSprite::getHardwareID(int _x, int _y, int _frame, int _count, int _xdiv, int _ydiv, int _zdiv, int _timeframe) {
uint v;
if(_count >= 0 && height <= 1 && width <= 1) {
v = _count;
} else {
v = ((((((_timeframe)*ydiv+_ydiv)*xdiv+_xdiv)*frames+_frame)*height+_y)*width+_x);
}
if(v >= numsprites) {
if(numsprites == 1) {
v = 0;
} else {
v %= numsprites;
}
}
return spriteList[v]->getHardwareID();
}
GameSprite::TemplateImage* GameSprite::getTemplateImage(int sprite_index, const Outfit& outfit) {
if(instanced_templates.empty()) {
TemplateImage* img = newd TemplateImage(this, sprite_index, outfit);
instanced_templates.push_back(img);
return img;
}
// While this is linear lookup, it is very rare for the list to contain more than 4-8 entries, so it's faster than a hashmap anyways.
for(std::list<TemplateImage*>::iterator iter = instanced_templates.begin();
iter != instanced_templates.end();
++iter)
{
TemplateImage* img = *iter;
if(img->sprite_index == sprite_index) {
uint32_t lookHash = img->lookHead << 24 | img->lookBody << 16 | img->lookLegs << 8 | img->lookFeet;
if(outfit.getColorHash() == lookHash) {
return img;
}
}
}
TemplateImage* img = newd TemplateImage(this, sprite_index, outfit);
instanced_templates.push_back(img);
return img;
}
GLuint GameSprite::getHardwareID(int _x, int _y, int _dir, const Outfit& _outfit, int _timeframe) {
uint v;
v = ((((_dir) * frames) * height+_y) * width+_x);
if(v >= numsprites) {
if(numsprites == 1) {
v = 0;
} else {
v %= numsprites;
}
}
if(frames > 1) { // Template
TemplateImage* img = getTemplateImage(v, _outfit);
return img->getHardwareID();
}
return spriteList[v]->getHardwareID();
}
wxMemoryDC* GameSprite::getDC(SpriteSize sz) {
if(!dc[sz]) {
const int bgshade = settings.getInteger(Config::ICON_BACKGROUND);
uint8_t* rgb = nullptr;
uint ht, wt;
if(width == 2 && height == 2) {
wt = 2, ht = 2;
rgb = newd uint8_t[2*2*32*32*3];
memset(rgb, bgshade, 2*2*32*32*3);
for(int cf = 0; cf != frames; ++cf) {
uint8_t* rgb32x32[2][2] = {
{spriteList[(cf*2+1)*2+1]->getRGBData(),spriteList[(cf*2+1)*2]->getRGBData()},
{spriteList[cf*4+1]->getRGBData(),spriteList[cf*4]->getRGBData()}
};
for(int cy = 0; cy < 64; ++cy) {
for(int cx = 0; cx < 64; ++cx) {
int cw = cx/32;
int ch = cy/32;
if(!rgb32x32[ch][cw]) continue;
int rx = cx%32;
int ry = cy%32;
uint8_t r = rgb32x32[ch][cw][32*3*ry + 3*rx + 0];
uint8_t g = rgb32x32[ch][cw][32*3*ry + 3*rx + 1];
uint8_t b = rgb32x32[ch][cw][32*3*ry + 3*rx + 2];
if(r == 0xFF && g == 0x00 && b == 0xFF) {
// Transparent.. let it pass
} else {
rgb[64*3*cy + 3*cx + 0] = r;
rgb[64*3*cy + 3*cx + 1] = g;
rgb[64*3*cy + 3*cx + 2] = b;
}
}
}
delete[] rgb32x32[0][0];
delete[] rgb32x32[0][1];
delete[] rgb32x32[1][0];
delete[] rgb32x32[1][1];
}
// (cf*2+ch)*2+cw
} else if(width == 2 && height == 1) {
wt = 2, ht = 2;
rgb = newd uint8_t[2*2*32*32*3];
memset(rgb, bgshade, 2*2*32*32*3);
for(int cf = 0; cf != frames; ++cf) {
uint8_t* rgb32x32[2] = {
spriteList[cf*2+1]->getRGBData(),
spriteList[cf*2+0]->getRGBData()
};
for(int cy = 16; cy < 48; ++cy) {
for(int cx = 0; cx < 64; ++cx) {
int cw = cx/32;
int rx = cx%32;
int ry = cy-16;
if(!rgb32x32[cw]) continue;
uint8_t r = rgb32x32[cw][32*3*ry + 3*rx + 0];
uint8_t g = rgb32x32[cw][32*3*ry + 3*rx + 1];
uint8_t b = rgb32x32[cw][32*3*ry + 3*rx + 2];
if(r == 0xFF && g == 0x00 && b == 0xFF) {
// Transparent.. let it pass
} else {
rgb[64*3*cy + 3*cx + 0] = r;
rgb[64*3*cy + 3*cx + 1] = g;
rgb[64*3*cy + 3*cx + 2] = b;
}
}
}
delete[] rgb32x32[0];
delete[] rgb32x32[1];
}
} else if(width == 1 && height == 2) {
wt = 2, ht = 2;
rgb = newd uint8_t[2*2*32*32*3];
memset(rgb, bgshade, 2*2*32*32*3);
for(int cf = 0; cf != frames; ++cf) {
uint8_t* rgb32x32[2] = {
spriteList[cf*2+1]->getRGBData(),
spriteList[cf*2+0]->getRGBData()
};
for(int cy = 0; cy < 64; ++cy) {
for(int cx = 16; cx < 48; ++cx) {
int ch = cy/32;
int rx = cx-16;
int ry = cy%32;
if(!rgb32x32[ch]) continue;
uint8_t r = rgb32x32[ch][32*3*ry + 3*rx + 0];
uint8_t g = rgb32x32[ch][32*3*ry + 3*rx + 1];
uint8_t b = rgb32x32[ch][32*3*ry + 3*rx + 2];
if(r == 0xFF && g == 0x00 && b == 0xFF) {
// Transparent.. let it pass
} else {
rgb[64*3*cy + 3*cx + 0] = r;
rgb[64*3*cy + 3*cx + 1] = g;
rgb[64*3*cy + 3*cx + 2] = b;
}
}
}
delete[] rgb32x32[0];
delete[] rgb32x32[1];
}
} else if(width == 1 && height == 1) {
wt = 1, ht = 1;
rgb = newd uint8_t[32*32*3];
memset(rgb, bgshade, 32*32*3);
for(int cf = 0; cf != frames; ++cf) {
uint8_t* rgb32x32 = spriteList[cf]->getRGBData();
if(!rgb32x32) continue;
for(int cy = 0; cy < 32; ++cy) {
for(int cx = 0; cx < 32; ++cx) {
uint8_t r = rgb32x32[32*3*cy + 3*cx + 0];
uint8_t g = rgb32x32[32*3*cy + 3*cx + 1];
uint8_t b = rgb32x32[32*3*cy + 3*cx + 2];
if(r == 0xFF && g == 0x00 && b == 0xFF) {
// Transparent.. let it pass
} else {
rgb[32*3*cy + 3*cx + 0] = r;
rgb[32*3*cy + 3*cx + 1] = g;
rgb[32*3*cy + 3*cx + 2] = b;
}
}
}
delete[] rgb32x32;
}
} else {
return nullptr;
}
// Now comes the resizing / antialiasing
if(sz == SPRITE_SIZE_16x16) {
uint8_t* rgb16x16 = reinterpret_cast<uint8_t*>(malloc(16*16*3));
uint pixels_per_line = 32*wt;
uint pixels_per_pixel = 2*wt;
uint bytes_per_line = 3*pixels_per_line;
uint bytes_per_pixel = 3*pixels_per_pixel;
for(uint y = 0; y < 16; ++y) {
for(uint x = 0; x < 16; ++x) {
uint r = 0, g = 0, b = 0, c = 0;
for(uint u = 0; u < 2; ++u) {
for(uint v = 0; v < 2; ++v) {
r += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 0];
g += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 1];
b += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 2];
//r += rgb[96 * (2*y + u) + (6*x + v*3) + 0];
//g += rgb[96 * (2*y + u) + (6*x + v*3) + 1];
//b += rgb[96 * (2*y + u) + (6*x + v*3) + 2];
++c;
}
}
ASSERT(c);
rgb16x16[48*y+x*3+0] = r/c;
rgb16x16[48*y+x*3+1] = g/c;
rgb16x16[48*y+x*3+2] = b/c;
}
}
wxImage img(16,16, rgb16x16);
wxBitmap bmp(img);
dc[sz] = newd wxMemoryDC(bmp);
delete[] rgb;
gui.gfx.addSpriteToCleanup(this);
} else if(sz == SPRITE_SIZE_32x32) {
uint8_t* rgb32x32 = reinterpret_cast<uint8_t*>(malloc(32*32*3));
uint pixels_per_line = 32*wt;
uint pixels_per_pixel = wt;
uint bytes_per_line = 3*pixels_per_line;
uint bytes_per_pixel = 3*pixels_per_pixel;
for(uint y = 0; y < 32; ++y) {
for(uint x = 0; x < 32; ++x) {
uint r = 0, g = 0, b = 0, c = 0;
for(uint u = 0; u < ht; ++u) {
for(uint v = 0; v < wt; ++v) {
r += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 0];
g += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 1];
b += rgb[bytes_per_line * (pixels_per_pixel*y + u) + (bytes_per_pixel*x + 3*v) + 2];
++c;
}
}
ASSERT(c);
rgb32x32[96*y+x*3+0] = r/c;
rgb32x32[96*y+x*3+1] = g/c;
rgb32x32[96*y+x*3+2] = b/c;
}
}
wxImage img(32,32, rgb32x32);
wxBitmap bmp(img);
dc[sz] = newd wxMemoryDC(bmp);
delete[] rgb;
gui.gfx.addSpriteToCleanup(this);
} else {
ASSERT(sz == 0);
}
}
return dc[sz];
}
void GameSprite::DrawTo(wxDC* dc, SpriteSize sz, int start_x, int start_y, int width, int height) {
if(width == -1) width = sz == SPRITE_SIZE_32x32? 32 : 16;
if(height == -1) height= sz == SPRITE_SIZE_32x32? 32 : 16;
wxDC* sdc = getDC(sz);
if(sdc)
{
dc->Blit(start_x, start_y, width, height, sdc, 0, 0, wxCOPY, true);
}
else
{
const wxBrush& b = dc->GetBrush();
dc->SetBrush(*wxRED_BRUSH);
dc->DrawRectangle(start_x, start_y, width, height);
dc->SetBrush(b);
}
}
GameSprite::Image::Image() :
isGLLoaded(false),
lastaccess(0)
{
}
GameSprite::Image::~Image() {
unloadGLTexture(0);
}
void GameSprite::Image::createGLTexture(GLuint whatid) {
ASSERT(!isGLLoaded);
uint8_t* rgba = getRGBAData();
if(!rgba) return;
isGLLoaded = true;
gui.gfx.loaded_textures += 1;
glBindTexture(GL_TEXTURE_2D, whatid);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Filtering
glTexParameteri(GL_TEXTURE_2D, 0x2802, 0x812F);
glTexParameteri(GL_TEXTURE_2D, 0x2803, 0x812F);
delete[] rgba;
#undef SPRITE_SIZE
}
void GameSprite::Image::unloadGLTexture(GLuint whatid) {
isGLLoaded = false;
gui.gfx.loaded_textures -= 1;
glDeleteTextures(1, &whatid);
}
void GameSprite::Image::visit() {
lastaccess = time(nullptr);
}
void GameSprite::Image::clean(int time) {
if(isGLLoaded && time - lastaccess > settings.getInteger(Config::TEXTURE_LONGEVITY)) {
unloadGLTexture(0);
}
}
GameSprite::NormalImage::NormalImage() :
id(0),
size(0),
dump(nullptr)
{
}
GameSprite::NormalImage::~NormalImage() {
delete[] dump;
}
void GameSprite::NormalImage::clean(int time) {
Image::clean(time);
if(time - lastaccess > 5 && !settings.getInteger(Config::USE_MEMCACHED_SPRITES)) { // We keep dumps around for 5 seconds.
delete[] dump;
dump = nullptr;
}
}
uint8_t* GameSprite::NormalImage::getRGBData() {
if(dump == nullptr) {
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
return nullptr;
} else {
if(!gui.gfx.loadSpriteDump(dump, size, id)) {
return nullptr;
}
}
}
uint8_t* rgb32x32 = newd uint8_t[32*32*3];
/* SPR dump format
* The spr format contains chunks, a chunk can either be transparent or contain pixel data.
* First 2 bytes (unsigned short) are read, which tells us how long the chunk is. One
* chunk can stretch several rows in the outputted image, for example, if the chunk is 400
* pixels long. We will have to wrap it over 14 rows in the image.
* If the chunk is transparent. Set that many pixels to be transparent.
* If the chunk is pixel data, read from the cursor that many pixels. One pixel is 3 bytes in
* in RGB aligned data (eg. char R, char B, char G) so if the unsigned short says 20, we
* read 20*3 = 60 bytes.
* Once we read one chunk, we switch to the other type of chunk (if we've just read a transparent
* chunk, we read a pixel chunk and vice versa). And then start over again.
* All sprites start with a transparent chunk.
*/
int bytes = 0;
int x = 0;
int y = 0;
uint16_t chunk_size;
while(bytes < size && y < 32) {
chunk_size = dump[bytes] | dump[bytes+1] << 8;
bytes += 2;
for(int i = 0; i < chunk_size; ++i) {
// Transparent pixel
rgb32x32[96*y+x*3+0] = 0xFF;
rgb32x32[96*y+x*3+1] = 0x00;
rgb32x32[96*y+x*3+2] = 0xFF;
x++;
if(x >= 32) {
x = 0;
++y;
if(y >= 32)
break;
}
}
if(bytes >= size || y >= 32)
break; // We're done
// Now comes a pixel chunk, read it!
chunk_size = dump[bytes] | dump[bytes+1] << 8;
bytes += 2;
//printf("Reading pixel chunk size %d\n", int(chunk_size));
for(int i = 0; i < chunk_size; ++i) {
uint8_t red = dump[bytes+0];
uint8_t green= dump[bytes+1];
uint8_t blue = dump[bytes+2];
rgb32x32[96*y+x*3+0] = red;
rgb32x32[96*y+x*3+1] = green;
rgb32x32[96*y+x*3+2] = blue;
bytes += 3;
x++;
if(x >= 32) {
x = 0;
++y;
if(y >= 32)
break;
}
}
}
// Fill up any trailing pixels
while(y<32 && x<32) {
rgb32x32[96*y+x*3+0] = 0xFF; // Transparent
rgb32x32[96*y+x*3+1] = 0x00; // Transparent
rgb32x32[96*y+x*3+2] = 0xFF; // Transparent
x++;
if(x >= 32) {
x = 0;
++y;
}
}
return rgb32x32;
}
uint8_t* GameSprite::NormalImage::getRGBAData() {
if(dump == nullptr) {
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
return nullptr;
} else {
if(!gui.gfx.loadSpriteDump(dump, size, id)) {
return nullptr;
}
}
}
uint8_t* rgba32x32 = newd uint8_t[32*32*4];
memset(rgba32x32, 0, 32*32*4);
/* SPR dump format
* The spr format contains chunks, a chunk can either be transparent or contain pixel data.
* First 2 bytes (unsigned short) are read, which tells us how long the chunk is. One
* chunk can stretch several rows in the outputted image, for example, if the chunk is 400
* pixels long. We will have to wrap it over 14 rows in the image.
* If the chunk is transparent. Set that many pixels to be transparent.
* If the chunk is pixel data, read from the cursor that many pixels. One pixel is 3 bytes in
* in RGB aligned data (eg. char R, char B, char G) so if the unsigned short says 20, we
* read 20*3 = 60 bytes.
* Once we read one chunk, we switch to the other type of chunk (if we've just read a transparent
* chunk, we read a pixel chunk and vice versa). And then start over again.
* All sprites start with a transparent chunk.
*/
int bytes = 0;
int x = 0;
int y = 0;
uint16_t chunk_size;
while(bytes < size && y < 32) {
chunk_size = dump[bytes] | dump[bytes+1] << 8;
//printf("pos:%d\n", bytes);
//printf("Reading transparent chunk size %d\n", int(chunk_size));
bytes += 2;
for(int i = 0; i < chunk_size; ++i) {
//printf("128*%d+%d*4+3\t= %d\n", y, x, 128*y+x*4+3);
rgba32x32[128*y+x*4+3] = 0x00; // Transparent pixel
x++;
if(x >= 32) {
x = 0;
y++;
if(y >= 32)
break;
}
}
if(bytes >= size || y >= 32)
break; // We're done
// Now comes a pixel chunk, read it!
chunk_size = dump[bytes] | dump[bytes+1] << 8;
bytes += 2;
//printf("Reading pixel chunk size %d\n", int(chunk_size));
for(int i = 0; i < chunk_size; ++i) {
uint8_t red = dump[bytes+0];
uint8_t green = dump[bytes+1];
uint8_t blue = dump[bytes+2];
rgba32x32[128*y+x*4+0] = red;
rgba32x32[128*y+x*4+1] = green;
rgba32x32[128*y+x*4+2] = blue;
rgba32x32[128*y+x*4+3] = 0xFF; //Opaque pixel
bytes += 3;
x++;
if(x >= 32) {
x = 0;
y++;
if(y >= 32)
break;
}
}
}
// Fill up any trailing pixels
while(y<32 && x<32) {
rgba32x32[128*y+x*4+3] = 0x00; // Transparent pixel
x++;
if(x >= 32) {
x = 0;
y++;
}
}
return rgba32x32;
}
GLuint GameSprite::NormalImage::getHardwareID() {
if(isGLLoaded == false) {
createGLTexture(id);
}
visit();
return id;
}
void GameSprite::NormalImage::createGLTexture(GLuint ignored) {
Image::createGLTexture(id);
}
void GameSprite::NormalImage::unloadGLTexture(GLuint ignored) {
Image::unloadGLTexture(id);
}
GameSprite::TemplateImage::TemplateImage(GameSprite* parent, int v, const Outfit& outfit) :
gl_tid(0),
parent(parent),
sprite_index(v),
lookHead(outfit.lookHead),
lookBody(outfit.lookBody),
lookLegs(outfit.lookLegs),
lookFeet(outfit.lookFeet)
{
}
GameSprite::TemplateImage::~TemplateImage() {
}
void GameSprite::TemplateImage::colorizePixel(uint8_t color, uint8_t& red, uint8_t& green, uint8_t& blue) {
// Thanks! Khaos, or was it mips? Hmmm... =)
uint8_t ro = (TemplateOutfitLookupTable[color] & 0xFF0000) >> 16; // rgb outfit
uint8_t go = (TemplateOutfitLookupTable[color] & 0xFF00) >> 8;
uint8_t bo = (TemplateOutfitLookupTable[color] & 0xFF);
red = (uint8_t)(red * (ro / 255.f));
green = (uint8_t)(green * (go / 255.f));
blue = (uint8_t)(blue * (bo / 255.f));
}
uint8_t* GameSprite::TemplateImage::getRGBData() {
uint8_t* rgbdata = parent->spriteList[sprite_index]->getRGBData();
uint8_t* template_rgbdata = parent->spriteList[sprite_index + parent->height * parent->width]->getRGBData();
if(!rgbdata) {
delete[] template_rgbdata;
return nullptr;
}
if(!template_rgbdata) {
delete[] rgbdata;
return nullptr;
}
if(lookHead > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookHead = 0;
}
if(lookBody > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookBody = 0;
}
if(lookLegs > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookLegs = 0;
}
if(lookFeet > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookFeet = 0;
}
for(int y = 0; y < 32; ++y) {
for(int x = 0; x < 32; ++x) {
uint8_t& red = rgbdata[y*32*3 + x*3 + 0];
uint8_t& green = rgbdata[y*32*3 + x*3 + 1];
uint8_t& blue = rgbdata[y*32*3 + x*3 + 2];
uint8_t& tred = template_rgbdata[y*32*3 + x*3 + 0];
uint8_t& tgreen = template_rgbdata[y*32*3 + x*3 + 1];
uint8_t& tblue = template_rgbdata[y*32*3 + x*3 + 2];
if(tred && tgreen && !tblue) { // yellow => head
colorizePixel(lookHead, red, green, blue);
} else if(tred && !tgreen && !tblue) { // red => body
colorizePixel(lookBody, red, green, blue);
} else if(!tred && tgreen && !tblue) { // green => legs
colorizePixel(lookLegs, red, green, blue);
} else if(!tred && !tgreen && tblue) { // blue => feet
colorizePixel(lookFeet, red, green, blue);
}
}
}
delete[] template_rgbdata;
return rgbdata;
}
uint8_t* GameSprite::TemplateImage::getRGBAData() {
uint8_t* rgbadata = parent->spriteList[sprite_index]->getRGBAData();
uint8_t* template_rgbdata = parent->spriteList[sprite_index + parent->height * parent->width]->getRGBData();
if(!rgbadata) {
delete[] template_rgbdata;
return nullptr;
}
if(!template_rgbdata) {
delete[] rgbadata;
return nullptr;
}
if(lookHead > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookHead = 0;
}
if(lookBody > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookBody = 0;
}
if(lookLegs > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookLegs = 0;
}
if(lookFeet > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
lookFeet = 0;
}
for(int y = 0; y < 32; ++y) {
for(int x = 0; x < 32; ++x) {
uint8_t& red = rgbadata[y*32*4 + x*4 + 0];
uint8_t& green = rgbadata[y*32*4 + x*4 + 1];
uint8_t& blue = rgbadata[y*32*4 + x*4 + 2];
uint8_t& tred = template_rgbdata[y*32*3 + x*3 + 0];
uint8_t& tgreen = template_rgbdata[y*32*3 + x*3 + 1];
uint8_t& tblue = template_rgbdata[y*32*3 + x*3 + 2];
if(tred && tgreen && !tblue) { // yellow => head
colorizePixel(lookHead, red, green, blue);
} else if(tred && !tgreen && !tblue) { // red => body
colorizePixel(lookBody, red, green, blue);
} else if(!tred && tgreen && !tblue) { // green => legs
colorizePixel(lookLegs, red, green, blue);
} else if(!tred && !tgreen && tblue) { // blue => feet
colorizePixel(lookFeet, red, green, blue);
}
}
}
delete[] template_rgbdata;
return rgbadata;
}
GLuint GameSprite::TemplateImage::getHardwareID() {
if(isGLLoaded == false) {
if(gl_tid == 0) {
gl_tid = gui.gfx.getFreeTextureID();
}
createGLTexture(gl_tid);
if(!isGLLoaded) {
return 0;
}
}
visit();
return gl_tid;
}
void GameSprite::TemplateImage::createGLTexture(GLuint unused) {
Image::createGLTexture(gl_tid);
}
void GameSprite::TemplateImage::unloadGLTexture(GLuint unused) {
Image::unloadGLTexture(gl_tid);
}