graphics.cpp

main 1751 lines · 45.1 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19
20#include "sprites.h"
21#include "graphics.h"
22#include "filehandle.h"
23#include "settings.h"
24#include "gui.h"
25#include "otml.h"
26
27#include <wx/mstream.h>
28#include <wx/stopwatch.h>
29#include <wx/dir.h>
30#include "pngfiles.h"
31
32#include "../brushes/door_normal.xpm"
33#include "../brushes/door_normal_small.xpm"
34#include "../brushes/door_locked.xpm"
35#include "../brushes/door_locked_small.xpm"
36#include "../brushes/door_magic.xpm"
37#include "../brushes/door_magic_small.xpm"
38#include "../brushes/door_quest.xpm"
39#include "../brushes/door_quest_small.xpm"
40#include "../brushes/door_normal_alt.xpm"
41#include "../brushes/door_normal_alt_small.xpm"
42#include "../brushes/door_archway.xpm"
43#include "../brushes/door_archway_small.xpm"
44
45// All 133 template colors
46static uint32_t TemplateOutfitLookupTable[] = {
47 0xFFFFFF,
48 0xFFD4BF,
49 0xFFE9BF,
50 0xFFFFBF,
51 0xE9FFBF,
52 0xD4FFBF,
53 0xBFFFBF,
54 0xBFFFD4,
55 0xBFFFE9,
56 0xBFFFFF,
57 0xBFE9FF,
58 0xBFD4FF,
59 0xBFBFFF,
60 0xD4BFFF,
61 0xE9BFFF,
62 0xFFBFFF,
63 0xFFBFE9,
64 0xFFBFD4,
65 0xFFBFBF,
66 0xDADADA,
67 0xBF9F8F,
68 0xBFAF8F,
69 0xBFBF8F,
70 0xAFBF8F,
71 0x9FBF8F,
72 0x8FBF8F,
73 0x8FBF9F,
74 0x8FBFAF,
75 0x8FBFBF,
76 0x8FAFBF,
77 0x8F9FBF,
78 0x8F8FBF,
79 0x9F8FBF,
80 0xAF8FBF,
81 0xBF8FBF,
82 0xBF8FAF,
83 0xBF8F9F,
84 0xBF8F8F,
85 0xB6B6B6,
86 0xBF7F5F,
87 0xBFAF8F,
88 0xBFBF5F,
89 0x9FBF5F,
90 0x7FBF5F,
91 0x5FBF5F,
92 0x5FBF7F,
93 0x5FBF9F,
94 0x5FBFBF,
95 0x5F9FBF,
96 0x5F7FBF,
97 0x5F5FBF,
98 0x7F5FBF,
99 0x9F5FBF,
100 0xBF5FBF,
101 0xBF5F9F,
102 0xBF5F7F,
103 0xBF5F5F,
104 0x919191,
105 0xBF6A3F,
106 0xBF943F,
107 0xBFBF3F,
108 0x94BF3F,
109 0x6ABF3F,
110 0x3FBF3F,
111 0x3FBF6A,
112 0x3FBF94,
113 0x3FBFBF,
114 0x3F94BF,
115 0x3F6ABF,
116 0x3F3FBF,
117 0x6A3FBF,
118 0x943FBF,
119 0xBF3FBF,
120 0xBF3F94,
121 0xBF3F6A,
122 0xBF3F3F,
123 0x6D6D6D,
124 0xFF5500,
125 0xFFAA00,
126 0xFFFF00,
127 0xAAFF00,
128 0x54FF00,
129 0x00FF00,
130 0x00FF54,
131 0x00FFAA,
132 0x00FFFF,
133 0x00A9FF,
134 0x0055FF,
135 0x0000FF,
136 0x5500FF,
137 0xA900FF,
138 0xFE00FF,
139 0xFF00AA,
140 0xFF0055,
141 0xFF0000,
142 0x484848,
143 0xBF3F00,
144 0xBF7F00,
145 0xBFBF00,
146 0x7FBF00,
147 0x3FBF00,
148 0x00BF00,
149 0x00BF3F,
150 0x00BF7F,
151 0x00BFBF,
152 0x007FBF,
153 0x003FBF,
154 0x0000BF,
155 0x3F00BF,
156 0x7F00BF,
157 0xBF00BF,
158 0xBF007F,
159 0xBF003F,
160 0xBF0000,
161 0x242424,
162 0x7F2A00,
163 0x7F5500,
164 0x7F7F00,
165 0x557F00,
166 0x2A7F00,
167 0x007F00,
168 0x007F2A,
169 0x007F55,
170 0x007F7F,
171 0x00547F,
172 0x002A7F,
173 0x00007F,
174 0x2A007F,
175 0x54007F,
176 0x7F007F,
177 0x7F0055,
178 0x7F002A,
179 0x7F0000,
180};
181
182GraphicManager::GraphicManager() :
183 client_version(nullptr),
184 unloaded(true),
185 dat_format(DAT_FORMAT_UNKNOWN),
186 otfi_found(false),
187 is_extended(false),
188 has_transparency(false),
189 has_frame_durations(false),
190 has_frame_groups(false),
191 loaded_textures(0),
192 lastclean(0) {
193 animation_timer = newd wxStopWatch();
194 animation_timer->Start();
195}
196
197GraphicManager::~GraphicManager() {
198 for (SpriteMap::iterator iter = sprite_space.begin(); iter != sprite_space.end(); ++iter) {
199 delete iter->second;
200 }
201
202 for (ImageMap::iterator iter = image_space.begin(); iter != image_space.end(); ++iter) {
203 delete iter->second;
204 }
205
206 delete animation_timer;
207}
208
209bool GraphicManager::hasTransparency() const {
210 return has_transparency;
211}
212
213bool GraphicManager::isUnloaded() const {
214 return unloaded;
215}
216
217GLuint GraphicManager::getFreeTextureID() {
218 static GLuint id_counter = 0x10000000;
219 return id_counter++; // This should (hopefully) never run out
220}
221
222void GraphicManager::clear() {
223 SpriteMap new_sprite_space;
224 for (SpriteMap::iterator iter = sprite_space.begin(); iter != sprite_space.end(); ++iter) {
225 if (iter->first >= 0) { // Don't clean internal sprites
226 delete iter->second;
227 } else {
228 new_sprite_space.insert(std::make_pair(iter->first, iter->second));
229 }
230 }
231
232 for (ImageMap::iterator iter = image_space.begin(); iter != image_space.end(); ++iter) {
233 delete iter->second;
234 }
235
236 sprite_space.swap(new_sprite_space);
237 image_space.clear();
238 cleanup_list.clear();
239
240 item_count = 0;
241 creature_count = 0;
242 loaded_textures = 0;
243 lastclean = time(nullptr);
244 spritefile = "";
245
246 unloaded = true;
247}
248
249void GraphicManager::cleanSoftwareSprites() {
250 for (SpriteMap::iterator iter = sprite_space.begin(); iter != sprite_space.end(); ++iter) {
251 if (iter->first >= 0) { // Don't clean internal sprites
252 iter->second->unloadDC();
253 }
254 }
255}
256
257Sprite* GraphicManager::getSprite(int id) {
258 SpriteMap::iterator it = sprite_space.find(id);
259 if (it != sprite_space.end()) {
260 return it->second;
261 }
262 return nullptr;
263}
264
265GameSprite* GraphicManager::getCreatureSprite(int id) {
266 if (id < 0) {
267 return nullptr;
268 }
269
270 SpriteMap::iterator it = sprite_space.find(id + item_count);
271 if (it != sprite_space.end()) {
272 return static_cast<GameSprite*>(it->second);
273 }
274 return nullptr;
275}
276
277uint16_t GraphicManager::getItemSpriteMaxID() const {
278 return item_count;
279}
280
281uint16_t GraphicManager::getCreatureSpriteMaxID() const {
282 return creature_count;
283}
284
285#define loadPNGFile(name) _wxGetBitmapFromMemory(name, sizeof(name))
286inline wxBitmap* _wxGetBitmapFromMemory(const unsigned char* data, int length) {
287 wxMemoryInputStream is(data, length);
288 wxImage img(is, "image/png");
289 if (!img.IsOk()) {
290 return nullptr;
291 }
292 return newd wxBitmap(img, -1);
293}
294
295bool GraphicManager::loadEditorSprites() {
296 // Unused graphics MIGHT be loaded here, but it's a neglectable loss
297 sprite_space[EDITOR_SPRITE_SELECTION_MARKER] = newd EditorSprite(
298 newd wxBitmap(selection_marker_xpm16x16),
299 newd wxBitmap(selection_marker_xpm32x32),
300 nullptr
301 );
302 sprite_space[EDITOR_SPRITE_BRUSH_CD_1x1] = newd EditorSprite(
303 loadPNGFile(circular_1_small_png),
304 loadPNGFile(circular_1_png),
305 nullptr
306 );
307 sprite_space[EDITOR_SPRITE_BRUSH_CD_3x3] = newd EditorSprite(
308 loadPNGFile(circular_2_small_png),
309 loadPNGFile(circular_2_png),
310 nullptr
311 );
312 sprite_space[EDITOR_SPRITE_BRUSH_CD_5x5] = newd EditorSprite(
313 loadPNGFile(circular_3_small_png),
314 loadPNGFile(circular_3_png),
315 nullptr
316 );
317 sprite_space[EDITOR_SPRITE_BRUSH_CD_7x7] = newd EditorSprite(
318 loadPNGFile(circular_4_small_png),
319 loadPNGFile(circular_4_png),
320 nullptr
321 );
322 sprite_space[EDITOR_SPRITE_BRUSH_CD_9x9] = newd EditorSprite(
323 loadPNGFile(circular_5_small_png),
324 loadPNGFile(circular_5_png),
325 nullptr
326 );
327 sprite_space[EDITOR_SPRITE_BRUSH_CD_15x15] = newd EditorSprite(
328 loadPNGFile(circular_6_small_png),
329 loadPNGFile(circular_6_png),
330 nullptr
331 );
332 sprite_space[EDITOR_SPRITE_BRUSH_CD_19x19] = newd EditorSprite(
333 loadPNGFile(circular_7_small_png),
334 loadPNGFile(circular_7_png),
335 nullptr
336 );
337 sprite_space[EDITOR_SPRITE_BRUSH_SD_1x1] = newd EditorSprite(
338 loadPNGFile(rectangular_1_small_png),
339 loadPNGFile(rectangular_1_png),
340 nullptr
341 );
342 sprite_space[EDITOR_SPRITE_BRUSH_SD_3x3] = newd EditorSprite(
343 loadPNGFile(rectangular_2_small_png),
344 loadPNGFile(rectangular_2_png),
345 nullptr
346 );
347 sprite_space[EDITOR_SPRITE_BRUSH_SD_5x5] = newd EditorSprite(
348 loadPNGFile(rectangular_3_small_png),
349 loadPNGFile(rectangular_3_png),
350 nullptr
351 );
352 sprite_space[EDITOR_SPRITE_BRUSH_SD_7x7] = newd EditorSprite(
353 loadPNGFile(rectangular_4_small_png),
354 loadPNGFile(rectangular_4_png),
355 nullptr
356 );
357 sprite_space[EDITOR_SPRITE_BRUSH_SD_9x9] = newd EditorSprite(
358 loadPNGFile(rectangular_5_small_png),
359 loadPNGFile(rectangular_5_png),
360 nullptr
361 );
362 sprite_space[EDITOR_SPRITE_BRUSH_SD_15x15] = newd EditorSprite(
363 loadPNGFile(rectangular_6_small_png),
364 loadPNGFile(rectangular_6_png),
365 nullptr
366 );
367 sprite_space[EDITOR_SPRITE_BRUSH_SD_19x19] = newd EditorSprite(
368 loadPNGFile(rectangular_7_small_png),
369 loadPNGFile(rectangular_7_png),
370 nullptr
371 );
372
373 sprite_space[EDITOR_SPRITE_OPTIONAL_BORDER_TOOL] = newd EditorSprite(
374 loadPNGFile(optional_border_small_png),
375 loadPNGFile(optional_border_png),
376 nullptr
377 );
378 sprite_space[EDITOR_SPRITE_ERASER] = newd EditorSprite(
379 loadPNGFile(eraser_small_png),
380 loadPNGFile(eraser_png),
381 nullptr
382 );
383 sprite_space[EDITOR_SPRITE_PZ_TOOL] = newd EditorSprite(
384 loadPNGFile(protection_zone_small_png),
385 loadPNGFile(protection_zone_png),
386 nullptr
387 );
388 sprite_space[EDITOR_SPRITE_PVPZ_TOOL] = newd EditorSprite(
389 loadPNGFile(pvp_zone_small_png),
390 loadPNGFile(pvp_zone_png),
391 nullptr
392 );
393 sprite_space[EDITOR_SPRITE_ZONE_TOOL] = newd EditorSprite(
394 loadPNGFile(zone_brush_small_png),
395 loadPNGFile(zone_brush_zone_png)
396 );
397 sprite_space[EDITOR_SPRITE_NOLOG_TOOL] = newd EditorSprite(
398 loadPNGFile(no_logout_small_png),
399 loadPNGFile(no_logout_png),
400 nullptr
401 );
402 sprite_space[EDITOR_SPRITE_NOPVP_TOOL] = newd EditorSprite(
403 loadPNGFile(no_pvp_small_png),
404 loadPNGFile(no_pvp_png),
405 nullptr
406 );
407
408 sprite_space[EDITOR_SPRITE_DOOR_NORMAL] = newd EditorSprite(
409 newd wxBitmap(door_normal_small_xpm),
410 newd wxBitmap(door_normal_xpm),
411 nullptr
412 );
413 sprite_space[EDITOR_SPRITE_DOOR_LOCKED] = newd EditorSprite(
414 newd wxBitmap(door_locked_small_xpm),
415 newd wxBitmap(door_locked_xpm),
416 nullptr
417 );
418 sprite_space[EDITOR_SPRITE_DOOR_MAGIC] = newd EditorSprite(
419 newd wxBitmap(door_magic_small_xpm),
420 newd wxBitmap(door_magic_xpm),
421 nullptr
422 );
423 sprite_space[EDITOR_SPRITE_DOOR_QUEST] = newd EditorSprite(
424 newd wxBitmap(door_quest_small_xpm),
425 newd wxBitmap(door_quest_xpm),
426 nullptr
427 );
428 sprite_space[EDITOR_SPRITE_DOOR_NORMAL_ALT] = newd EditorSprite(
429 newd wxBitmap(door_normal_alt_small_xpm),
430 newd wxBitmap(door_normal_alt_xpm),
431 nullptr
432 );
433 sprite_space[EDITOR_SPRITE_DOOR_ARCHWAY] = newd EditorSprite(
434 newd wxBitmap(door_archway_small_xpm),
435 newd wxBitmap(door_archway_xpm),
436 nullptr
437 );
438 sprite_space[EDITOR_SPRITE_WINDOW_NORMAL] = newd EditorSprite(
439 loadPNGFile(window_normal_small_png),
440 loadPNGFile(window_normal_png),
441 nullptr
442 );
443 sprite_space[EDITOR_SPRITE_WINDOW_HATCH] = newd EditorSprite(
444 loadPNGFile(window_hatch_small_png),
445 loadPNGFile(window_hatch_png),
446 nullptr
447 );
448
449 sprite_space[EDITOR_SPRITE_SELECTION_GEM] = newd EditorSprite(
450 loadPNGFile(gem_edit_png),
451 nullptr,
452 nullptr
453 );
454 sprite_space[EDITOR_SPRITE_DRAWING_GEM] = newd EditorSprite(
455 loadPNGFile(gem_move_png),
456 nullptr,
457 nullptr
458 );
459
460 return true;
461}
462
463bool GraphicManager::loadOTFI(const FileName& filename, wxString& error, wxArrayString& warnings) {
464 wxDir dir(filename.GetFullPath());
465 wxString otfi_file;
466
467 otfi_found = false;
468
469 if (dir.GetFirst(&otfi_file, "*.otfi", wxDIR_FILES)) {
470 wxFileName otfi(filename.GetFullPath(), otfi_file);
471 OTMLDocumentPtr doc = OTMLDocument::parse(otfi.GetFullPath().ToStdString());
472 if (doc->size() == 0 || !doc->hasChildAt("DatSpr")) {
473 error += "'DatSpr' tag not found";
474 return false;
475 }
476
477 OTMLNodePtr node = doc->get("DatSpr");
478 is_extended = node->valueAt<bool>("extended");
479 has_transparency = node->valueAt<bool>("transparency");
480 has_frame_durations = node->valueAt<bool>("frame-durations");
481 has_frame_groups = node->valueAt<bool>("frame-groups");
482 std::string metadata = node->valueAt<std::string>("metadata-file", std::string(ASSETS_NAME) + ".dat");
483 std::string sprites = node->valueAt<std::string>("sprites-file", std::string(ASSETS_NAME) + ".spr");
484 metadata_file = wxFileName(filename.GetFullPath(), wxString(metadata));
485 sprites_file = wxFileName(filename.GetFullPath(), wxString(sprites));
486 otfi_found = true;
487 }
488
489 if (!otfi_found) {
490 is_extended = false;
491 has_transparency = false;
492 has_frame_durations = false;
493 has_frame_groups = false;
494 metadata_file = wxFileName(filename.GetFullPath(), wxString(ASSETS_NAME) + ".dat");
495 sprites_file = wxFileName(filename.GetFullPath(), wxString(ASSETS_NAME) + ".spr");
496 }
497
498 return true;
499}
500
501bool GraphicManager::loadSpriteMetadata(const FileName& datafile, wxString& error, wxArrayString& warnings) {
502 // items.otb has most of the info we need. This only loads the GameSprite metadata
503 FileReadHandle file(nstr(datafile.GetFullPath()));
504
505 if (!file.isOk()) {
506 error += "Failed to open " + datafile.GetFullPath() + " for reading\nThe error reported was:" + wxstr(file.getErrorMessage());
507 return false;
508 }
509
510 uint16_t effect_count, distance_count;
511
512 uint32_t datSignature;
513 file.getU32(datSignature);
514 // get max id
515 file.getU16(item_count);
516 file.getU16(creature_count);
517 file.getU16(effect_count);
518 file.getU16(distance_count);
519
520 uint32_t minID = 100; // items start with id 100
521 // We don't load distance/effects, if we would, just add effect_count & distance_count here
522 uint32_t maxID = item_count + creature_count;
523
524 dat_format = client_version->getDatFormatForSignature(datSignature);
525
526 if (!otfi_found) {
527 is_extended = dat_format >= DAT_FORMAT_96;
528 has_frame_durations = dat_format >= DAT_FORMAT_1050;
529 has_frame_groups = dat_format >= DAT_FORMAT_1057;
530 }
531
532 uint16_t id = minID;
533 // loop through all ItemDatabase until we reach the end of file
534 while (id <= maxID) {
535 GameSprite* sType = newd GameSprite();
536 sprite_space[id] = sType;
537
538 sType->id = id;
539
540 // Load the sprite flags
541 if (!loadSpriteMetadataFlags(file, sType, error, warnings)) {
542 wxString msg;
543 msg << "Failed to load flags for sprite " << sType->id;
544 warnings.push_back(msg);
545 }
546
547 // Reads the group count
548 uint8_t group_count = 1;
549 if (has_frame_groups && id > item_count) {
550 file.getU8(group_count);
551 }
552
553 for (uint32_t k = 0; k < group_count; ++k) {
554 // Skipping the group type
555 if (has_frame_groups && id > item_count) {
556 file.skip(1);
557 }
558
559 // Size and GameSprite data
560 file.getByte(sType->width);
561 file.getByte(sType->height);
562
563 // Skipping the exact size
564 if ((sType->width > 1) || (sType->height > 1)) {
565 file.skip(1);
566 }
567
568 file.getU8(sType->layers); // Number of blendframes (some sprites consist of several merged sprites)
569 file.getU8(sType->pattern_x);
570 file.getU8(sType->pattern_y);
571 if (dat_format <= DAT_FORMAT_74) {
572 sType->pattern_z = 1;
573 } else {
574 file.getU8(sType->pattern_z);
575 }
576 file.getU8(sType->frames); // Length of animation
577
578 if (sType->frames > 1) {
579 uint8_t async = 0;
580 int loop_count = 0;
581 int8_t start_frame = 0;
582 if (has_frame_durations) {
583 file.getByte(async);
584 file.get32(loop_count);
585 file.getSByte(start_frame);
586 }
587 sType->animator = newd Animator(sType->frames, start_frame, loop_count, async == 1);
588 if (has_frame_durations) {
589 for (int i = 0; i < sType->frames; i++) {
590 uint32_t min;
591 uint32_t max;
592 file.getU32(min);
593 file.getU32(max);
594 FrameDuration* frame_duration = sType->animator->getFrameDuration(i);
595 frame_duration->setValues(int(min), int(max));
596 }
597 sType->animator->reset();
598 }
599 }
600
601 sType->numsprites = (int)sType->width * (int)sType->height * (int)sType->layers * (int)sType->pattern_x * (int)sType->pattern_y * sType->pattern_z * (int)sType->frames;
602
603 // Read the sprite ids
604 for (uint32_t i = 0; i < sType->numsprites; ++i) {
605 uint32_t sprite_id;
606 if (is_extended) {
607 file.getU32(sprite_id);
608 } else {
609 uint16_t u16 = 0;
610 file.getU16(u16);
611 sprite_id = u16;
612 }
613
614 if (image_space[sprite_id] == nullptr) {
615 GameSprite::NormalImage* img = newd GameSprite::NormalImage();
616 img->id = sprite_id;
617 image_space[sprite_id] = img;
618 }
619 sType->spriteList.push_back(static_cast<GameSprite::NormalImage*>(image_space[sprite_id]));
620 }
621 }
622 ++id;
623 }
624
625 return true;
626}
627
628bool GraphicManager::loadSpriteMetadataFlags(FileReadHandle& file, GameSprite* sType, wxString& error, wxArrayString& warnings) {
629 uint8_t prev_flag = 0;
630 uint8_t flag = DatFlagLast;
631
632 for (int i = 0; i < DatFlagLast; ++i) {
633 prev_flag = flag;
634 file.getU8(flag);
635
636 if (flag == DatFlagLast) {
637 return true;
638 }
639 if (dat_format >= DAT_FORMAT_1010) {
640 /* In 10.10+ all attributes from 16 and up were
641 * incremented by 1 to make space for 16 as
642 * "No Movement Animation" flag.
643 */
644 if (flag == 16) {
645 flag = DatFlagNoMoveAnimation;
646 } else if (flag > 16) {
647 flag -= 1;
648 }
649 } else if (dat_format >= DAT_FORMAT_86) {
650 /* Default attribute values follow
651 * the format of 8.6-9.86.
652 * Therefore no changes here.
653 */
654 } else if (dat_format >= DAT_FORMAT_78) {
655 /* In 7.80-8.54 all attributes from 8 and higher were
656 * incremented by 1 to make space for 8 as
657 * "Item Charges" flag.
658 */
659 if (flag == 8) {
660 flag = DatFlagChargeable;
661 } else if (flag > 8) {
662 flag -= 1;
663 }
664 } else if (dat_format >= DAT_FORMAT_755) {
665 /* In 7.55-7.72 attributes 23 is "Floor Change". */
666 if (flag == 23) {
667 flag = DatFlagFloorChange;
668 }
669 } else if (dat_format >= DAT_FORMAT_74) {
670 /* In 7.4-7.5 attribute "Ground Border" did not exist
671 * attributes 1-15 have to be adjusted.
672 * Several other changes in the format.
673 */
674 if (flag > 0 && flag <= 15) {
675 flag += 1;
676 } else if (flag == 16) {
677 flag = DatFlagLight;
678 } else if (flag == 17) {
679 flag = DatFlagFloorChange;
680 } else if (flag == 18) {
681 flag = DatFlagFullGround;
682 } else if (flag == 19) {
683 flag = DatFlagElevation;
684 } else if (flag == 20) {
685 flag = DatFlagDisplacement;
686 } else if (flag == 22) {
687 flag = DatFlagMinimapColor;
688 } else if (flag == 23) {
689 flag = DatFlagRotateable;
690 } else if (flag == 24) {
691 flag = DatFlagLyingCorpse;
692 } else if (flag == 25) {
693 flag = DatFlagHangable;
694 } else if (flag == 26) {
695 flag = DatFlagHookSouth;
696 } else if (flag == 27) {
697 flag = DatFlagHookEast;
698 } else if (flag == 28) {
699 flag = DatFlagAnimateAlways;
700 }
701
702 /* "Multi Use" and "Force Use" are swapped */
703 if (flag == DatFlagMultiUse) {
704 flag = DatFlagForceUse;
705 } else if (flag == DatFlagForceUse) {
706 flag = DatFlagMultiUse;
707 }
708 }
709
710 switch (flag) {
711 case DatFlagGroundBorder:
712 case DatFlagOnBottom:
713 case DatFlagOnTop:
714 case DatFlagContainer:
715 case DatFlagStackable:
716 case DatFlagForceUse:
717 case DatFlagMultiUse:
718 case DatFlagFluidContainer:
719 case DatFlagSplash:
720 case DatFlagNotWalkable:
721 case DatFlagNotMoveable:
722 case DatFlagBlockProjectile:
723 case DatFlagNotPathable:
724 case DatFlagPickupable:
725 case DatFlagHangable:
726 case DatFlagHookSouth:
727 case DatFlagHookEast:
728 case DatFlagRotateable:
729 case DatFlagDontHide:
730 case DatFlagTranslucent:
731 case DatFlagLyingCorpse:
732 case DatFlagAnimateAlways:
733 case DatFlagFullGround:
734 case DatFlagLook:
735 case DatFlagWrappable:
736 case DatFlagUnwrappable:
737 case DatFlagTopEffect:
738 case DatFlagFloorChange:
739 case DatFlagNoMoveAnimation:
740 case DatFlagChargeable:
741 break;
742
743 case DatFlagGround:
744 case DatFlagWritable:
745 case DatFlagWritableOnce:
746 case DatFlagCloth:
747 case DatFlagLensHelp:
748 case DatFlagUsable:
749 file.skip(2);
750 break;
751
752 case DatFlagLight: {
753 SpriteLight light;
754 uint16_t intensity;
755 uint16_t color;
756 file.getU16(intensity);
757 file.getU16(color);
758 sType->has_light = true;
759 sType->light = SpriteLight { static_cast<uint8_t>(intensity), static_cast<uint8_t>(color) };
760 break;
761 }
762
763 case DatFlagDisplacement: {
764 if (dat_format >= DAT_FORMAT_755) {
765 uint16_t offset_x;
766 uint16_t offset_y;
767 file.getU16(offset_x);
768 file.getU16(offset_y);
769
770 sType->drawoffset_x = offset_x;
771 sType->drawoffset_y = offset_y;
772 } else {
773 sType->drawoffset_x = 8;
774 sType->drawoffset_y = 8;
775 }
776 break;
777 }
778
779 case DatFlagElevation: {
780 uint16_t draw_height;
781 file.getU16(draw_height);
782 sType->draw_height = draw_height;
783 break;
784 }
785
786 case DatFlagMinimapColor: {
787 uint16_t minimap_color;
788 file.getU16(minimap_color);
789 sType->minimap_color = minimap_color;
790 break;
791 }
792
793 case DatFlagMarket: {
794 file.skip(6);
795 std::string marketName;
796 file.getString(marketName);
797 file.skip(4);
798 break;
799 }
800
801 default: {
802 wxString err;
803 err << "Metadata: Unknown flag: " << i2ws(flag) << ". Previous flag: " << i2ws(prev_flag) << ".";
804 warnings.push_back(err);
805 break;
806 }
807 }
808 }
809
810 return true;
811}
812
813bool GraphicManager::loadSpriteData(const FileName& datafile, wxString& error, wxArrayString& warnings) {
814 FileReadHandle fh(nstr(datafile.GetFullPath()));
815
816 if (!fh.isOk()) {
817 error = "Failed to open file for reading";
818 return false;
819 }
820
821#define safe_get(func, ...) \
822 do { \
823 if (!fh.get##func(__VA_ARGS__)) { \
824 error = wxstr(fh.getErrorMessage()); \
825 return false; \
826 } \
827 } while (false)
828
829 uint32_t sprSignature;
830 safe_get(U32, sprSignature);
831
832 uint32_t total_pics = 0;
833 if (is_extended) {
834 safe_get(U32, total_pics);
835 } else {
836 uint16_t u16 = 0;
837 safe_get(U16, u16);
838 total_pics = u16;
839 }
840
841 if (!g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
842 spritefile = nstr(datafile.GetFullPath());
843 unloaded = false;
844 return true;
845 }
846
847 std::vector<uint32_t> sprite_indexes;
848 for (uint32_t i = 0; i < total_pics; ++i) {
849 uint32_t index;
850 safe_get(U32, index);
851 sprite_indexes.push_back(index);
852 }
853
854 // Now read individual sprites
855 int id = 1;
856 for (std::vector<uint32_t>::iterator sprite_iter = sprite_indexes.begin(); sprite_iter != sprite_indexes.end(); ++sprite_iter, ++id) {
857 uint32_t index = *sprite_iter + 3;
858 fh.seek(index);
859 uint16_t size;
860 safe_get(U16, size);
861
862 ImageMap::iterator it = image_space.find(id);
863 if (it != image_space.end()) {
864 GameSprite::NormalImage* spr = dynamic_cast<GameSprite::NormalImage*>(it->second);
865 if (spr && size > 0) {
866 if (spr->size > 0) {
867 wxString ss;
868 ss << "items.spr: Duplicate GameSprite id " << id;
869 warnings.push_back(ss);
870 fh.seekRelative(size);
871 } else {
872 spr->id = id;
873 spr->size = size;
874 spr->dump = newd uint8_t[size];
875 if (!fh.getRAW(spr->dump, size)) {
876 error = wxstr(fh.getErrorMessage());
877 return false;
878 }
879 }
880 }
881 } else {
882 fh.seekRelative(size);
883 }
884 }
885#undef safe_get
886 unloaded = false;
887 return true;
888}
889
890bool GraphicManager::loadSpriteDump(uint8_t*& target, uint16_t& size, int sprite_id) {
891 if (g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
892 return false;
893 }
894
895 if (sprite_id == 0) {
896 // Empty GameSprite
897 size = 0;
898 target = nullptr;
899 return true;
900 }
901
902 FileReadHandle fh(spritefile);
903 if (!fh.isOk()) {
904 return false;
905 }
906 unloaded = false;
907
908 if (!fh.seek((is_extended ? 4 : 2) + sprite_id * sizeof(uint32_t))) {
909 return false;
910 }
911
912 uint32_t to_seek = 0;
913 if (fh.getU32(to_seek)) {
914 fh.seek(to_seek + 3);
915 uint16_t sprite_size;
916 if (fh.getU16(sprite_size)) {
917 target = newd uint8_t[sprite_size];
918 if (fh.getRAW(target, sprite_size)) {
919 size = sprite_size;
920 return true;
921 }
922 delete[] target;
923 target = nullptr;
924 }
925 }
926 return false;
927}
928
929void GraphicManager::addSpriteToCleanup(GameSprite* spr) {
930 cleanup_list.push_back(spr);
931 // Clean if needed
932 if (cleanup_list.size() > std::max<uint32_t>(100, g_settings.getInteger(Config::SOFTWARE_CLEAN_THRESHOLD))) {
933 for (int i = 0; i < g_settings.getInteger(Config::SOFTWARE_CLEAN_SIZE) && static_cast<uint32_t>(i) < cleanup_list.size(); ++i) {
934 cleanup_list.front()->unloadDC();
935 cleanup_list.pop_front();
936 }
937 }
938}
939
940void GraphicManager::garbageCollection() {
941 if (g_settings.getInteger(Config::TEXTURE_MANAGEMENT)) {
942 int t = time(nullptr);
943 if (loaded_textures > g_settings.getInteger(Config::TEXTURE_CLEAN_THRESHOLD) && t - lastclean > g_settings.getInteger(Config::TEXTURE_CLEAN_PULSE)) {
944 ImageMap::iterator iit = image_space.begin();
945 while (iit != image_space.end()) {
946 iit->second->clean(t);
947 ++iit;
948 }
949 SpriteMap::iterator sit = sprite_space.begin();
950 while (sit != sprite_space.end()) {
951 GameSprite* gs = dynamic_cast<GameSprite*>(sit->second);
952 if (gs) {
953 gs->clean(t);
954 }
955 ++sit;
956 }
957 lastclean = t;
958 }
959 }
960}
961
962EditorSprite::EditorSprite(wxBitmap* b16x16, wxBitmap* b32x32, wxBitmap* b64x64) {
963 bm[SPRITE_SIZE_16x16] = b16x16;
964 bm[SPRITE_SIZE_32x32] = b32x32;
965 bm[SPRITE_SIZE_64x64] = b64x64;
966}
967
968EditorSprite::~EditorSprite() {
969 unloadDC();
970}
971
972void EditorSprite::DrawTo(wxDC* dc, SpriteSize sz, int start_x, int start_y, int width, int height) {
973 wxBitmap* sp = bm[sz];
974 if (sp) {
975 dc->DrawBitmap(*sp, start_x, start_y, true);
976 } else if (sz == SPRITE_SIZE_64x64 && bm[SPRITE_SIZE_32x32]) {
977 // Fallback to 32x32 and scale it up if no 64x64 sprite is available
978 wxImage img = bm[SPRITE_SIZE_32x32]->ConvertToImage();
979 img.Rescale(64, 64, wxIMAGE_QUALITY_HIGH);
980 wxBitmap scaled(img);
981 dc->DrawBitmap(scaled, start_x, start_y, true);
982 }
983}
984
985void EditorSprite::unloadDC() {
986 delete bm[SPRITE_SIZE_16x16];
987 delete bm[SPRITE_SIZE_32x32];
988 delete bm[SPRITE_SIZE_64x64];
989 bm[SPRITE_SIZE_16x16] = nullptr;
990 bm[SPRITE_SIZE_32x32] = nullptr;
991 bm[SPRITE_SIZE_64x64] = nullptr;
992}
993
994GameSprite::GameSprite() :
995 id(0),
996 height(0),
997 width(0),
998 layers(0),
999 pattern_x(0),
1000 pattern_y(0),
1001 pattern_z(0),
1002 frames(0),
1003 numsprites(0),
1004 animator(nullptr),
1005 draw_height(0),
1006 drawoffset_x(0),
1007 drawoffset_y(0),
1008 minimap_color(0) {
1009 dc[SPRITE_SIZE_16x16] = nullptr;
1010 dc[SPRITE_SIZE_32x32] = nullptr;
1011}
1012
1013GameSprite::~GameSprite() {
1014 unloadDC();
1015 for (std::list<TemplateImage*>::iterator iter = instanced_templates.begin(); iter != instanced_templates.end(); ++iter) {
1016 delete *iter;
1017 }
1018
1019 delete animator;
1020}
1021
1022void GameSprite::clean(int time) {
1023 for (std::list<TemplateImage*>::iterator iter = instanced_templates.begin();
1024 iter != instanced_templates.end();
1025 ++iter) {
1026 (*iter)->clean(time);
1027 }
1028}
1029
1030void GameSprite::unloadDC() {
1031 delete dc[SPRITE_SIZE_16x16];
1032 delete dc[SPRITE_SIZE_32x32];
1033 dc[SPRITE_SIZE_16x16] = nullptr;
1034 dc[SPRITE_SIZE_32x32] = nullptr;
1035}
1036
1037int GameSprite::getDrawHeight() const {
1038 return draw_height;
1039}
1040
1041std::pair<int, int> GameSprite::getDrawOffset() const {
1042 return std::make_pair(drawoffset_x, drawoffset_y);
1043}
1044
1045uint8_t GameSprite::getMiniMapColor() const {
1046 return minimap_color;
1047}
1048
1049int GameSprite::getIndex(int width, int height, int layer, int pattern_x, int pattern_y, int pattern_z, int frame) const {
1050 return ((((((frame % this->frames) * this->pattern_z + pattern_z) * this->pattern_y + pattern_y) * this->pattern_x + pattern_x) * this->layers + layer) * this->height + height) * this->width + width;
1051}
1052
1053GLuint GameSprite::getHardwareID(int _x, int _y, int _layer, int _count, int _pattern_x, int _pattern_y, int _pattern_z, int _frame) {
1054 uint32_t v;
1055 if (_count >= 0 && height <= 1 && width <= 1) {
1056 v = _count;
1057 } else {
1058 v = ((((((_frame)*pattern_y + _pattern_y) * pattern_x + _pattern_x) * layers + _layer) * height + _y) * width + _x);
1059 }
1060 if (v >= numsprites) {
1061 if (numsprites == 1) {
1062 v = 0;
1063 } else {
1064 v %= numsprites;
1065 }
1066 }
1067 return spriteList[v]->getHardwareID();
1068}
1069
1070GameSprite::TemplateImage* GameSprite::getTemplateImage(int sprite_index, const Outfit& outfit) {
1071 if (instanced_templates.empty()) {
1072 TemplateImage* img = newd TemplateImage(this, sprite_index, outfit);
1073 instanced_templates.push_back(img);
1074 return img;
1075 }
1076 // 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.
1077 for (std::list<TemplateImage*>::iterator iter = instanced_templates.begin(); iter != instanced_templates.end(); ++iter) {
1078 TemplateImage* img = *iter;
1079 if (img->sprite_index == sprite_index) {
1080 uint32_t lookHash = img->lookHead << 24 | img->lookBody << 16 | img->lookLegs << 8 | img->lookFeet;
1081 if (outfit.getColorHash() == lookHash) {
1082 return img;
1083 }
1084 }
1085 }
1086 TemplateImage* img = newd TemplateImage(this, sprite_index, outfit);
1087 instanced_templates.push_back(img);
1088 return img;
1089}
1090
1091GLuint GameSprite::getHardwareID(int _x, int _y, int _dir, int _addon, int _pattern_z, const Outfit& _outfit, int _frame) {
1092 uint32_t v = getIndex(_x, _y, 0, _dir, _addon, _pattern_z, _frame);
1093 if (v >= numsprites) {
1094 if (numsprites == 1) {
1095 v = 0;
1096 } else {
1097 v %= numsprites;
1098 }
1099 }
1100 if (layers > 1) { // Template
1101 TemplateImage* img = getTemplateImage(v, _outfit);
1102 return img->getHardwareID();
1103 }
1104 return spriteList[v]->getHardwareID();
1105}
1106
1107wxMemoryDC* GameSprite::getDC(SpriteSize size) {
1108 ASSERT(size == SPRITE_SIZE_16x16 || size == SPRITE_SIZE_32x32);
1109
1110 if (!dc[size]) {
1111 ASSERT(width >= 1 && height >= 1);
1112
1113 const int bgshade = g_settings.getInteger(Config::ICON_BACKGROUND);
1114
1115 int image_size = std::max<int>(width, height) * SPRITE_PIXELS;
1116 wxImage image(image_size, image_size);
1117 image.Clear(bgshade);
1118
1119 for (uint8_t l = 0; l < layers; l++) {
1120 for (uint8_t w = 0; w < width; w++) {
1121 for (uint8_t h = 0; h < height; h++) {
1122 const int i = getIndex(w, h, l, 0, 0, 0, 0);
1123 uint8_t* data = spriteList[i]->getRGBData();
1124 if (data) {
1125 wxImage img(SPRITE_PIXELS, SPRITE_PIXELS, data);
1126 img.SetMaskColour(0xFF, 0x00, 0xFF);
1127 image.Paste(img, (width - w - 1) * SPRITE_PIXELS, (height - h - 1) * SPRITE_PIXELS);
1128 img.Destroy();
1129 }
1130 }
1131 }
1132 }
1133
1134 // Now comes the resizing / antialiasing
1135 if (size == SPRITE_SIZE_16x16 || image.GetWidth() > SPRITE_PIXELS || image.GetHeight() > SPRITE_PIXELS) {
1136 int new_size = SPRITE_SIZE_16x16 ? 16 : 32;
1137 image.Rescale(new_size, new_size);
1138 }
1139
1140 wxBitmap bmp(image);
1141 dc[size] = newd wxMemoryDC(bmp);
1142 g_gui.gfx.addSpriteToCleanup(this);
1143 image.Destroy();
1144 }
1145 return dc[size];
1146}
1147
1148void GameSprite::DrawTo(wxDC* dc, SpriteSize sz, int start_x, int start_y, int width, int height) {
1149 if (width == -1) {
1150 width = (sz == SPRITE_SIZE_32x32) ? 32 : (sz == SPRITE_SIZE_64x64 ? 64 : 16);
1151 }
1152 if (height == -1) {
1153 height = (sz == SPRITE_SIZE_32x32) ? 32 : (sz == SPRITE_SIZE_64x64 ? 64 : 16);
1154 }
1155
1156 if (sz == SPRITE_SIZE_64x64) {
1157 // For 64x64, use the 32x32 DC and scale it
1158 wxDC* sdc = getDC(SPRITE_SIZE_32x32);
1159 if (sdc) {
1160 wxBitmap bmp(32, 32);
1161 wxMemoryDC temp_dc(bmp);
1162 temp_dc.Blit(0, 0, 32, 32, sdc, 0, 0, wxCOPY, true);
1163 temp_dc.SelectObject(wxNullBitmap);
1164
1165 wxImage img = bmp.ConvertToImage();
1166 img.Rescale(64, 64, wxIMAGE_QUALITY_HIGH);
1167 wxBitmap scaled(img);
1168
1169 dc->DrawBitmap(scaled, start_x, start_y, true);
1170 } else {
1171 const wxBrush& b = dc->GetBrush();
1172 dc->SetBrush(*wxRED_BRUSH);
1173 dc->DrawRectangle(start_x, start_y, width, height);
1174 dc->SetBrush(b);
1175 }
1176 } else {
1177 // Regular 16x16 or 32x32 rendering
1178 wxDC* sdc = getDC(sz);
1179 if (sdc) {
1180 dc->Blit(start_x, start_y, width, height, sdc, 0, 0, wxCOPY, true);
1181 } else {
1182 const wxBrush& b = dc->GetBrush();
1183 dc->SetBrush(*wxRED_BRUSH);
1184 dc->DrawRectangle(start_x, start_y, width, height);
1185 dc->SetBrush(b);
1186 }
1187 }
1188}
1189
1190void GameSprite::DrawOutfit(wxDC* dc, SpriteSize sz, int start_x, int start_y, const Outfit& outfit) {
1191 // This implementation is similar to DrawTo but will use templated images when available
1192
1193 // First, check if this GameSprite supports outfit coloring
1194 // If not, just use the regular DrawTo method
1195 if (!layers || !frames || !pattern_x || !pattern_y || !pattern_z) {
1196 DrawTo(dc, sz, start_x, start_y);
1197 return;
1198 }
1199
1200 // Get the first directional pose of this creature (south direction)
1201 int sprite_index = getIndex(width, height, 0, 0, 0, 0, 0);
1202
1203 // Try to get a templated image for this outfit
1204 TemplateImage* img = nullptr;
1205 if (outfit.lookType > 0 && (outfit.lookHead > 0 || outfit.lookBody > 0 ||
1206 outfit.lookLegs > 0 || outfit.lookFeet > 0)) {
1207 // Attempt to find or create a template image with these colors
1208 img = getTemplateImage(sprite_index, outfit);
1209 }
1210
1211 if (img) {
1212 // If we have a templated image with outfit colors, use it
1213 if (sz == SPRITE_SIZE_64x64) {
1214 // For 64x64, use the 32x32 DC and scale it
1215 wxDC* sdc = getDC(SPRITE_SIZE_32x32);
1216 if (sdc) {
1217 wxBitmap bmp(32, 32);
1218 wxMemoryDC temp_dc(bmp);
1219 temp_dc.Blit(0, 0, 32, 32, sdc, 0, 0, wxCOPY, true);
1220 temp_dc.SelectObject(wxNullBitmap);
1221
1222 wxImage img = bmp.ConvertToImage();
1223 img.Rescale(64, 64, wxIMAGE_QUALITY_HIGH);
1224 wxBitmap scaled(img);
1225
1226 dc->DrawBitmap(scaled, start_x, start_y, true);
1227 } else {
1228 // Fallback to regular drawing
1229 DrawTo(dc, sz, start_x, start_y);
1230 }
1231 } else {
1232 // Regular 16x16 or 32x32 rendering
1233 wxDC* sdc = getDC(sz);
1234 if (sdc) {
1235 dc->Blit(start_x, start_y,
1236 (sz == SPRITE_SIZE_32x32 ? 32 : 16),
1237 (sz == SPRITE_SIZE_32x32 ? 32 : 16),
1238 sdc, 0, 0, wxCOPY, true);
1239 } else {
1240 // Fallback to regular drawing
1241 DrawTo(dc, sz, start_x, start_y);
1242 }
1243 }
1244 } else {
1245 // Fallback to regular drawing if no template is available
1246 DrawTo(dc, sz, start_x, start_y);
1247 }
1248}
1249
1250GameSprite::Image::Image() :
1251 isGLLoaded(false),
1252 lastaccess(0) {
1253 ////
1254}
1255
1256GameSprite::Image::~Image() {
1257 unloadGLTexture(0);
1258}
1259
1260void GameSprite::Image::createGLTexture(GLuint whatid) {
1261 ASSERT(!isGLLoaded);
1262
1263 uint8_t* rgba = getRGBAData();
1264 if (!rgba) {
1265 return;
1266 }
1267
1268 isGLLoaded = true;
1269 g_gui.gfx.loaded_textures += 1;
1270
1271 glBindTexture(GL_TEXTURE_2D, whatid);
1272 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Filtering
1273 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Filtering
1274 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0x812F); // GL_CLAMP_TO_EDGE
1275 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 0x812F); // GL_CLAMP_TO_EDGE
1276 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SPRITE_PIXELS, SPRITE_PIXELS, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
1277
1278 delete[] rgba;
1279#undef SPRITE_SIZE
1280}
1281
1282void GameSprite::Image::unloadGLTexture(GLuint whatid) {
1283 isGLLoaded = false;
1284 g_gui.gfx.loaded_textures -= 1;
1285 glDeleteTextures(1, &whatid);
1286}
1287
1288void GameSprite::Image::visit() {
1289 lastaccess = time(nullptr);
1290}
1291
1292void GameSprite::Image::clean(int time) {
1293 if (isGLLoaded && time - lastaccess > g_settings.getInteger(Config::TEXTURE_LONGEVITY)) {
1294 unloadGLTexture(0);
1295 }
1296}
1297
1298GameSprite::NormalImage::NormalImage() :
1299 id(0),
1300 size(0),
1301 dump(nullptr) {
1302 ////
1303}
1304
1305GameSprite::NormalImage::~NormalImage() {
1306 delete[] dump;
1307}
1308
1309void GameSprite::NormalImage::clean(int time) {
1310 Image::clean(time);
1311 if (time - lastaccess > 5 && !g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) { // We keep dumps around for 5 seconds.
1312 delete[] dump;
1313 dump = nullptr;
1314 }
1315}
1316
1317uint8_t* GameSprite::NormalImage::getRGBData() {
1318 if (!dump) {
1319 if (g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
1320 return nullptr;
1321 }
1322
1323 if (!g_gui.gfx.loadSpriteDump(dump, size, id)) {
1324 return nullptr;
1325 }
1326 }
1327
1328 const int pixels_data_size = SPRITE_PIXELS * SPRITE_PIXELS * 3;
1329 uint8_t* data = newd uint8_t[pixels_data_size];
1330 uint8_t bpp = g_gui.gfx.hasTransparency() ? 4 : 3;
1331 int write = 0;
1332 int read = 0;
1333
1334 // decompress pixels
1335 while (read < size && write < pixels_data_size) {
1336 int transparent = dump[read] | dump[read + 1] << 8;
1337 read += 2;
1338 for (int i = 0; i < transparent && write < pixels_data_size; i++) {
1339 data[write + 0] = 0xFF; // red
1340 data[write + 1] = 0x00; // green
1341 data[write + 2] = 0xFF; // blue
1342 write += 3;
1343 }
1344
1345 int colored = dump[read] | dump[read + 1] << 8;
1346 read += 2;
1347 for (int i = 0; i < colored && write < pixels_data_size; i++) {
1348 data[write + 0] = dump[read + 0]; // red
1349 data[write + 1] = dump[read + 1]; // green
1350 data[write + 2] = dump[read + 2]; // blue
1351 write += 3;
1352 read += bpp;
1353 }
1354 }
1355
1356 // fill remaining pixels
1357 while (write < pixels_data_size) {
1358 data[write + 0] = 0xFF; // red
1359 data[write + 1] = 0x00; // green
1360 data[write + 2] = 0xFF; // blue
1361 write += 3;
1362 }
1363 return data;
1364}
1365
1366uint8_t* GameSprite::NormalImage::getRGBAData() {
1367 if (!dump) {
1368 if (g_settings.getInteger(Config::USE_MEMCACHED_SPRITES)) {
1369 return nullptr;
1370 }
1371
1372 if (!g_gui.gfx.loadSpriteDump(dump, size, id)) {
1373 return nullptr;
1374 }
1375 }
1376
1377 const int pixels_data_size = SPRITE_PIXELS_SIZE * 4;
1378 uint8_t* data = newd uint8_t[pixels_data_size];
1379 bool use_alpha = g_gui.gfx.hasTransparency();
1380 uint8_t bpp = use_alpha ? 4 : 3;
1381 int write = 0;
1382 int read = 0;
1383
1384 // decompress pixels
1385 while (read < size && write < pixels_data_size) {
1386 int transparent = dump[read] | dump[read + 1] << 8;
1387 if (use_alpha && transparent >= SPRITE_PIXELS_SIZE) { // Corrupted sprite?
1388 break;
1389 }
1390 read += 2;
1391 for (int i = 0; i < transparent && write < pixels_data_size; i++) {
1392 data[write + 0] = 0x00; // red
1393 data[write + 1] = 0x00; // green
1394 data[write + 2] = 0x00; // blue
1395 data[write + 3] = 0x00; // alpha
1396 write += 4;
1397 }
1398
1399 int colored = dump[read] | dump[read + 1] << 8;
1400 read += 2;
1401 for (int i = 0; i < colored && write < pixels_data_size; i++) {
1402 data[write + 0] = dump[read + 0]; // red
1403 data[write + 1] = dump[read + 1]; // green
1404 data[write + 2] = dump[read + 2]; // blue
1405 data[write + 3] = use_alpha ? dump[read + 3] : 0xFF; // alpha
1406 write += 4;
1407 read += bpp;
1408 }
1409 }
1410
1411 // fill remaining pixels
1412 while (write < pixels_data_size) {
1413 data[write + 0] = 0x00; // red
1414 data[write + 1] = 0x00; // green
1415 data[write + 2] = 0x00; // blue
1416 data[write + 3] = 0x00; // alpha
1417 write += 4;
1418 }
1419 return data;
1420}
1421
1422GLuint GameSprite::NormalImage::getHardwareID() {
1423 if (!isGLLoaded) {
1424 createGLTexture(id);
1425 }
1426 visit();
1427 return id;
1428}
1429
1430void GameSprite::NormalImage::createGLTexture(GLuint ignored) {
1431 Image::createGLTexture(id);
1432}
1433
1434void GameSprite::NormalImage::unloadGLTexture(GLuint ignored) {
1435 Image::unloadGLTexture(id);
1436}
1437
1438GameSprite::TemplateImage::TemplateImage(GameSprite* parent, int v, const Outfit& outfit) :
1439 gl_tid(0),
1440 parent(parent),
1441 sprite_index(v),
1442 lookHead(outfit.lookHead),
1443 lookBody(outfit.lookBody),
1444 lookLegs(outfit.lookLegs),
1445 lookFeet(outfit.lookFeet) {
1446 ////
1447}
1448
1449GameSprite::TemplateImage::~TemplateImage() {
1450 ////
1451}
1452
1453void GameSprite::TemplateImage::colorizePixel(uint8_t color, uint8_t& red, uint8_t& green, uint8_t& blue) {
1454 // Thanks! Khaos, or was it mips? Hmmm... =)
1455 uint8_t ro = (TemplateOutfitLookupTable[color] & 0xFF0000) >> 16; // rgb outfit
1456 uint8_t go = (TemplateOutfitLookupTable[color] & 0xFF00) >> 8;
1457 uint8_t bo = (TemplateOutfitLookupTable[color] & 0xFF);
1458 red = (uint8_t)(red * (ro / 255.f));
1459 green = (uint8_t)(green * (go / 255.f));
1460 blue = (uint8_t)(blue * (bo / 255.f));
1461}
1462
1463uint8_t* GameSprite::TemplateImage::getRGBData() {
1464 uint8_t* rgbdata = parent->spriteList[sprite_index]->getRGBData();
1465 uint8_t* template_rgbdata = parent->spriteList[sprite_index + parent->height * parent->width]->getRGBData();
1466
1467 if (!rgbdata) {
1468 delete[] template_rgbdata;
1469 return nullptr;
1470 }
1471 if (!template_rgbdata) {
1472 delete[] rgbdata;
1473 return nullptr;
1474 }
1475
1476 if (lookHead > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1477 lookHead = 0;
1478 }
1479 if (lookBody > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1480 lookBody = 0;
1481 }
1482 if (lookLegs > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1483 lookLegs = 0;
1484 }
1485 if (lookFeet > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1486 lookFeet = 0;
1487 }
1488
1489 for (int y = 0; y < SPRITE_PIXELS; ++y) {
1490 for (int x = 0; x < SPRITE_PIXELS; ++x) {
1491 uint8_t& red = rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 0];
1492 uint8_t& green = rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 1];
1493 uint8_t& blue = rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 2];
1494
1495 uint8_t& tred = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 0];
1496 uint8_t& tgreen = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 1];
1497 uint8_t& tblue = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 2];
1498
1499 if (tred && tgreen && !tblue) { // yellow => head
1500 colorizePixel(lookHead, red, green, blue);
1501 } else if (tred && !tgreen && !tblue) { // red => body
1502 colorizePixel(lookBody, red, green, blue);
1503 } else if (!tred && tgreen && !tblue) { // green => legs
1504 colorizePixel(lookLegs, red, green, blue);
1505 } else if (!tred && !tgreen && tblue) { // blue => feet
1506 colorizePixel(lookFeet, red, green, blue);
1507 }
1508 }
1509 }
1510 delete[] template_rgbdata;
1511 return rgbdata;
1512}
1513
1514uint8_t* GameSprite::TemplateImage::getRGBAData() {
1515 uint8_t* rgbadata = parent->spriteList[sprite_index]->getRGBAData();
1516 uint8_t* template_rgbdata = parent->spriteList[sprite_index + parent->height * parent->width]->getRGBData();
1517
1518 if (!rgbadata) {
1519 delete[] template_rgbdata;
1520 return nullptr;
1521 }
1522 if (!template_rgbdata) {
1523 delete[] rgbadata;
1524 return nullptr;
1525 }
1526
1527 if (lookHead > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1528 lookHead = 0;
1529 }
1530 if (lookBody > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1531 lookBody = 0;
1532 }
1533 if (lookLegs > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1534 lookLegs = 0;
1535 }
1536 if (lookFeet > (sizeof(TemplateOutfitLookupTable) / sizeof(TemplateOutfitLookupTable[0]))) {
1537 lookFeet = 0;
1538 }
1539
1540 for (int y = 0; y < SPRITE_PIXELS; ++y) {
1541 for (int x = 0; x < SPRITE_PIXELS; ++x) {
1542 uint8_t& red = rgbadata[y * SPRITE_PIXELS * 4 + x * 4 + 0];
1543 uint8_t& green = rgbadata[y * SPRITE_PIXELS * 4 + x * 4 + 1];
1544 uint8_t& blue = rgbadata[y * SPRITE_PIXELS * 4 + x * 4 + 2];
1545
1546 uint8_t& tred = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 0];
1547 uint8_t& tgreen = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 1];
1548 uint8_t& tblue = template_rgbdata[y * SPRITE_PIXELS * 3 + x * 3 + 2];
1549
1550 if (tred && tgreen && !tblue) { // yellow => head
1551 colorizePixel(lookHead, red, green, blue);
1552 } else if (tred && !tgreen && !tblue) { // red => body
1553 colorizePixel(lookBody, red, green, blue);
1554 } else if (!tred && tgreen && !tblue) { // green => legs
1555 colorizePixel(lookLegs, red, green, blue);
1556 } else if (!tred && !tgreen && tblue) { // blue => feet
1557 colorizePixel(lookFeet, red, green, blue);
1558 }
1559 }
1560 }
1561 delete[] template_rgbdata;
1562 return rgbadata;
1563}
1564
1565GLuint GameSprite::TemplateImage::getHardwareID() {
1566 if (!isGLLoaded) {
1567 if (gl_tid == 0) {
1568 gl_tid = g_gui.gfx.getFreeTextureID();
1569 }
1570 createGLTexture(gl_tid);
1571 if (!isGLLoaded) {
1572 return 0;
1573 }
1574 }
1575 visit();
1576 return gl_tid;
1577}
1578
1579void GameSprite::TemplateImage::createGLTexture(GLuint unused) {
1580 Image::createGLTexture(gl_tid);
1581}
1582
1583void GameSprite::TemplateImage::unloadGLTexture(GLuint unused) {
1584 Image::unloadGLTexture(gl_tid);
1585}
1586
1587// ============================================================================
1588// Animator
1589
1590Animator::Animator(int frame_count, int start_frame, int loop_count, bool async) :
1591 frame_count(frame_count),
1592 start_frame(start_frame),
1593 loop_count(loop_count),
1594 async(async),
1595 current_frame(0),
1596 current_loop(0),
1597 current_duration(0),
1598 total_duration(0),
1599 direction(ANIMATION_FORWARD),
1600 last_time(0),
1601 is_complete(false) {
1602 ASSERT(start_frame >= -1 && start_frame < frame_count);
1603
1604 for (int i = 0; i < frame_count; i++) {
1605 durations.push_back(newd FrameDuration(ITEM_FRAME_DURATION, ITEM_FRAME_DURATION));
1606 }
1607
1608 reset();
1609}
1610
1611Animator::~Animator() {
1612 for (int i = 0; i < frame_count; i++) {
1613 delete durations[i];
1614 }
1615 durations.clear();
1616}
1617
1618int Animator::getStartFrame() const {
1619 if (start_frame > -1) {
1620 return start_frame;
1621 }
1622 return uniform_random(0, frame_count - 1);
1623}
1624
1625FrameDuration* Animator::getFrameDuration(int frame) {
1626 ASSERT(frame >= 0 && frame < frame_count);
1627 return durations[frame];
1628}
1629
1630int Animator::getFrame() {
1631 long time = g_gui.gfx.getElapsedTime();
1632 if (time != last_time && !is_complete) {
1633 long elapsed = time - last_time;
1634 if (elapsed >= current_duration) {
1635 int frame = 0;
1636 if (loop_count < 0) {
1637 frame = getPingPongFrame();
1638 } else {
1639 frame = getLoopFrame();
1640 }
1641
1642 if (current_frame != frame) {
1643 int duration = getDuration(frame) - (elapsed - current_duration);
1644 if (duration < 0 && !async) {
1645 calculateSynchronous();
1646 } else {
1647 current_frame = frame;
1648 current_duration = std::max<int>(0, duration);
1649 }
1650 } else {
1651 is_complete = true;
1652 }
1653 } else {
1654 current_duration -= elapsed;
1655 }
1656
1657 last_time = time;
1658 }
1659 return current_frame;
1660}
1661
1662void Animator::setFrame(int frame) {
1663 ASSERT(frame == -1 || frame == 255 || frame == 254 || (frame >= 0 && frame < frame_count));
1664
1665 if (current_frame == frame) {
1666 return;
1667 }
1668
1669 if (async) {
1670 if (frame == 255) { // Async mode
1671 current_frame = 0;
1672 } else if (frame == 254) { // Random mode
1673 current_frame = uniform_random(0, frame_count - 1);
1674 } else if (frame >= 0 && frame < frame_count) {
1675 current_frame = frame;
1676 } else {
1677 current_frame = getStartFrame();
1678 }
1679
1680 is_complete = false;
1681 last_time = g_gui.gfx.getElapsedTime();
1682 current_duration = getDuration(current_frame);
1683 current_loop = 0;
1684 } else {
1685 calculateSynchronous();
1686 }
1687}
1688
1689void Animator::reset() {
1690 total_duration = 0;
1691 for (int i = 0; i < frame_count; i++) {
1692 total_duration += durations[i]->max;
1693 }
1694
1695 is_complete = false;
1696 direction = ANIMATION_FORWARD;
1697 current_loop = 0;
1698 async = false;
1699 setFrame(-1);
1700}
1701
1702int Animator::getDuration(int frame) const {
1703 ASSERT(frame >= 0 && frame < frame_count);
1704 return durations[frame]->getDuration();
1705}
1706
1707int Animator::getPingPongFrame() {
1708 int count = direction == ANIMATION_FORWARD ? 1 : -1;
1709 int next_frame = current_frame + count;
1710 if (next_frame < 0 || next_frame >= frame_count) {
1711 direction = direction == ANIMATION_FORWARD ? ANIMATION_BACKWARD : ANIMATION_FORWARD;
1712 count *= -1;
1713 }
1714 return current_frame + count;
1715}
1716
1717int Animator::getLoopFrame() {
1718 int next_phase = current_frame + 1;
1719 if (next_phase < frame_count) {
1720 return next_phase;
1721 }
1722
1723 if (loop_count == 0) {
1724 return 0;
1725 }
1726
1727 if (current_loop < (loop_count - 1)) {
1728 current_loop++;
1729 return 0;
1730 }
1731 return current_frame;
1732}
1733
1734void Animator::calculateSynchronous() {
1735 long time = g_gui.gfx.getElapsedTime();
1736 if (time > 0 && total_duration > 0) {
1737 long elapsed = time % total_duration;
1738 int total_time = 0;
1739 for (int i = 0; i < frame_count; i++) {
1740 int duration = getDuration(i);
1741 if (elapsed >= total_time && elapsed < total_time + duration) {
1742 current_frame = i;
1743 current_duration = duration - (elapsed - total_time);
1744 break;
1745 }
1746 total_time += duration;
1747 }
1748 last_time = time;
1749 }
1750}
1751
Top