mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
Clean git repository since the old svn / git mess was pretty much unrecoverable unfortunetly. I would have liked to keep the history for posterity, but that proved to be a futile quest.
1915 lines
49 KiB
C++
1915 lines
49 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() :
|
|
unloaded(true),
|
|
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(NULL);
|
|
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 NULL;
|
|
}
|
|
|
|
GameSprite* GraphicManager::getCreatureSprite(int id)
|
|
{
|
|
if(id < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SpriteMap::iterator it = sprite_space.find(id+item_count);
|
|
if(it != sprite_space.end())
|
|
{
|
|
return static_cast<GameSprite*>(it->second);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
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 NULL;
|
|
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),
|
|
NULL
|
|
);
|
|
sprite_space[EDITOR_SPRITE_DRAWING_GEM] =
|
|
newd EditorSprite(
|
|
loadPNGFile(gem_move_png),
|
|
NULL
|
|
);
|
|
|
|
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 datVersion;
|
|
file.getU32(datVersion);
|
|
//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;
|
|
|
|
|
|
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;
|
|
|
|
// read the options until we find a 0xff
|
|
uint8_t lastopt;
|
|
uint8_t optbyte = 0xff;
|
|
|
|
do
|
|
{
|
|
lastopt = optbyte;
|
|
file.getByte(optbyte);
|
|
if(datVersion >= 0x4C2C7993UL)
|
|
{
|
|
// >= 8.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: //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: // ??
|
|
file.skip(2);
|
|
break;
|
|
/*
|
|
case 0x56: // ??
|
|
file.skip(1);
|
|
break;
|
|
case 0x7f: // ??
|
|
break;
|
|
case 0x95: // ??
|
|
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;
|
|
}
|
|
}
|
|
else if(datVersion > 0x439D5A34UL)
|
|
{
|
|
// > 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;
|
|
}
|
|
}
|
|
else if(datVersion == 0x439D5A33 || datVersion == 0x439D5A34UL)
|
|
{
|
|
// 7.6
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//7.4
|
|
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);
|
|
|
|
// 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 < 0x439D5A33UL) // Pre 7.6
|
|
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) {
|
|
uint16_t sprite_id;
|
|
file.getU16(sprite_id);
|
|
if(image_space[sprite_id] == NULL) {
|
|
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::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;
|
|
}
|
|
uint32_t sprVersion = 0;
|
|
uint16_t total_pics = 0;
|
|
|
|
#define safe_get(func, ...) do {\
|
|
if(!fh.get##func(__VA_ARGS__)) {\
|
|
error = wxstr(fh.getErrorMessage()); \
|
|
return false; \
|
|
} \
|
|
} while(false)
|
|
|
|
|
|
safe_get(U32, sprVersion);
|
|
|
|
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES) == false) {
|
|
spritefile = nstr(datafile.GetFullPath());
|
|
unloaded = false;
|
|
return true;
|
|
}
|
|
|
|
safe_get(U16, total_pics);
|
|
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);
|
|
}
|
|
// Read all header data, 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 = NULL;
|
|
return true;
|
|
}
|
|
|
|
FileReadHandle fh(spritefile);
|
|
if(fh.isOk() == false)
|
|
return false;
|
|
unloaded = false;
|
|
if(!fh.seek(2 + sprite_id * sizeof(uint32_t)))
|
|
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 = NULL;
|
|
}
|
|
}
|
|
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(NULL);
|
|
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] = NULL;
|
|
bm[SPRITE_SIZE_32x32] = NULL;
|
|
}
|
|
|
|
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] = NULL;
|
|
dc[SPRITE_SIZE_32x32] = NULL;
|
|
}
|
|
|
|
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] = NULL;
|
|
dc[SPRITE_SIZE_32x32] = NULL;
|
|
}
|
|
|
|
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 = NULL;
|
|
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 NULL;
|
|
}
|
|
|
|
// 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(NULL);
|
|
}
|
|
|
|
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(NULL)
|
|
{
|
|
}
|
|
|
|
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 = NULL;
|
|
}
|
|
}
|
|
|
|
uint8_t* GameSprite::NormalImage::getRGBData() {
|
|
if(dump == NULL) {
|
|
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
|
|
return NULL;
|
|
} else {
|
|
if(!gui.gfx.loadSpriteDump(dump, size, id)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
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 == NULL) {
|
|
if(settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
|
|
return NULL;
|
|
} else {
|
|
if(!gui.gfx.loadSpriteDump(dump, size, id)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
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 NULL;
|
|
}
|
|
if(!template_rgbdata) {
|
|
delete[] rgbdata;
|
|
return NULL;
|
|
}
|
|
|
|
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 NULL;
|
|
}
|
|
if(!template_rgbdata) {
|
|
delete[] rgbadata;
|
|
return NULL;
|
|
}
|
|
|
|
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);
|
|
}
|