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#ifdef __APPLE__
21 #include <GLUT/glut.h>
22#else
23 #include <GL/glut.h>
24#endif
25
26#include "editor.h"
27#include "gui.h"
28#include "sprites.h"
29#include "map_drawer.h"
30#include "map_display.h"
31#include "copybuffer.h"
32#include "live_socket.h"
33#include "graphics.h"
34
35#include "doodad_brush.h"
36#include "creature_brush.h"
37#include "house_exit_brush.h"
38#include "house_brush.h"
39#include "spawn_brush.h"
40#include "wall_brush.h"
41#include "carpet_brush.h"
42#include "raw_brush.h"
43#include "table_brush.h"
44#include "waypoint_brush.h"
45#include "light_drawer.h"
46
47using Color = std::tuple<int, int, int>;
48
49static std::vector<Color> colors;
50void GenerateColors() {
51 int r = 250, g = 100, b = 100;
52 const int step = 25;
53 bool incrementing = true;
54
55 while (true) {
56 if (std::find(colors.begin(), colors.end(), Color({ r, g, b })) == colors.end()) {
57 colors.push_back({ r, g, b });
58 }
59
60 if (g < 250 && incrementing) {
61 g += step;
62 } else if (r > 100 && !incrementing && g == 250) {
63 r -= step;
64 } else if (b < 250 && r == 100) {
65 b += step;
66 } else if (g > 100 && b == 250) {
67 g -= step;
68 } else if (r < 250 && g == 100) {
69 r += step;
70 } else if (b > 100 && r == 250) {
71 b -= step;
72 } else if (b == 100 && g == 250) {
73 incrementing = false;
74 }
75
76 if (r == 250 && g == 100 && b == 100 && !incrementing) {
77 break;
78 }
79 }
80}
81
82DrawingOptions::DrawingOptions() {
83 SetDefault();
84 GenerateColors();
85}
86
87void DrawingOptions::SetDefault() {
88 transparent_floors = false;
89 transparent_items = false;
90 show_ingame_box = false;
91 show_lights = false;
92 show_light_str = true;
93 show_tech_items = true;
94 show_waypoints = true;
95 ingame = false;
96 dragging = false;
97
98 show_grid = 0;
99 show_all_floors = true;
100 show_creatures = true;
101 show_spawns = true;
102 show_houses = true;
103 show_shade = true;
104 show_special_tiles = true;
105 show_zone_areas = true;
106 show_items = true;
107
108 highlight_items = false;
109 highlight_locked_doors = true;
110 show_blocking = false;
111 show_tooltips = false;
112 show_as_minimap = false;
113 show_only_colors = false;
114 show_only_modified = false;
115 show_preview = false;
116 show_hooks = false;
117 hide_items_when_zoomed = true;
118}
119
120void DrawingOptions::SetIngame() {
121 transparent_floors = false;
122 transparent_items = false;
123 show_ingame_box = false;
124 show_lights = false;
125 show_light_str = false;
126 show_tech_items = false;
127 show_waypoints = false;
128 ingame = true;
129 dragging = false;
130
131 show_grid = 0;
132 show_all_floors = true;
133 show_creatures = true;
134 show_spawns = false;
135 show_houses = false;
136 show_shade = false;
137 show_special_tiles = false;
138 show_zone_areas = false;
139 show_items = true;
140
141 highlight_items = false;
142 highlight_locked_doors = false;
143 show_blocking = false;
144 show_tooltips = false;
145 show_as_minimap = false;
146 show_only_colors = false;
147 show_only_modified = false;
148 show_preview = false;
149 show_hooks = false;
150 hide_items_when_zoomed = false;
151}
152
153bool DrawingOptions::isDrawLight() const noexcept {
154 return show_lights;
155}
156
157MapDrawer::MapDrawer(MapCanvas* canvas) :
158 canvas(canvas), editor(canvas->editor) {
159 light_drawer = std::make_shared<LightDrawer>();
160}
161
162MapDrawer::~MapDrawer() {
163 Release();
164}
165
166void MapDrawer::SetupVars() {
167 canvas->MouseToMap(&mouse_map_x, &mouse_map_y);
168 canvas->GetViewBox(&view_scroll_x, &view_scroll_y, &screensize_x, &screensize_y);
169
170 dragging = canvas->dragging;
171 dragging_draw = canvas->dragging_draw;
172
173 zoom = (float)canvas->GetZoom();
174 tile_size = int(TileSize / zoom); // after zoom
175 floor = canvas->GetFloor();
176
177 if (options.show_all_floors) {
178 if (floor <= GROUND_LAYER) {
179 start_z = GROUND_LAYER;
180 } else {
181 start_z = std::min(MAP_MAX_LAYER, floor + 2);
182 }
183 } else {
184 start_z = floor;
185 }
186
187 end_z = floor;
188 superend_z = (floor > GROUND_LAYER ? 8 : 0);
189
190 start_x = view_scroll_x / TileSize;
191 start_y = view_scroll_y / TileSize;
192
193 if (floor > GROUND_LAYER) {
194 start_x -= 2;
195 start_y -= 2;
196 }
197
198 end_x = start_x + screensize_x / tile_size + 2;
199 end_y = start_y + screensize_y / tile_size + 2;
200}
201
202void MapDrawer::SetupGL() {
203 glViewport(0, 0, screensize_x, screensize_y);
204
205 // Enable 2D mode
206 int vPort[4];
207
208 glGetIntegerv(GL_VIEWPORT, vPort);
209
210 glMatrixMode(GL_PROJECTION);
211 glPushMatrix();
212 glLoadIdentity();
213 glOrtho(0, vPort[2] * zoom, vPort[3] * zoom, 0, -1, 1);
214
215 glMatrixMode(GL_MODELVIEW);
216 glPushMatrix();
217 glLoadIdentity();
218 glTranslatef(0.375f, 0.375f, 0.0f);
219}
220
221void MapDrawer::Release() {
222 for (std::vector<MapTooltip*>::const_iterator it = tooltips.begin(); it != tooltips.end(); ++it) {
223 delete *it;
224 }
225 tooltips.clear();
226
227 if (light_drawer) {
228 light_drawer->clear();
229 }
230
231 // Disable 2D mode
232 glMatrixMode(GL_PROJECTION);
233 glPopMatrix();
234 glMatrixMode(GL_MODELVIEW);
235 glPopMatrix();
236}
237
238void MapDrawer::Draw() {
239 DrawBackground();
240 DrawMap();
241 if (options.isDrawLight()) {
242 DrawLight();
243 }
244 DrawDraggingShadow();
245 DrawHigherFloors();
246 if (options.dragging) {
247 DrawSelectionBox();
248 }
249 DrawLiveCursors();
250 DrawBrush();
251 if (options.show_grid) {
252 DrawGrid();
253 }
254 if (options.show_ingame_box) {
255 DrawIngameBox();
256 }
257 if (options.show_tooltips) {
258 DrawTooltips();
259 }
260}
261
262void MapDrawer::DrawBackground() {
263 // Black Background
264 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
265
266 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
267 glLoadIdentity();
268
269 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
270 glEnable(GL_BLEND);
271
272 // glAlphaFunc(GL_GEQUAL, 0.9f);
273 // glEnable(GL_ALPHA_TEST);
274}
275
276inline int getFloorAdjustment(int floor) {
277 if (floor > GROUND_LAYER) { // Underground
278 return 0; // No adjustment
279 } else {
280 return TileSize * (GROUND_LAYER - floor);
281 }
282}
283
284void MapDrawer::DrawMap() {
285 int center_x = start_x + int(screensize_x * zoom / 64);
286 int center_y = start_y + int(screensize_y * zoom / 64);
287 int offset_y = 2;
288 int box_start_map_x = center_x - view_scroll_x;
289 int box_start_map_y = center_y - view_scroll_x + offset_y;
290 int box_end_map_x = center_x + ClientMapWidth;
291 int box_end_map_y = center_y + ClientMapHeight + offset_y;
292
293 bool live_client = editor.IsLiveClient();
294
295 Brush* brush = g_gui.GetCurrentBrush();
296
297 // The current house we're drawing
298 current_house_id = 0;
299 if (brush) {
300 if (brush->isHouse()) {
301 current_house_id = brush->asHouse()->getHouseID();
302 } else if (brush->isHouseExit()) {
303 current_house_id = brush->asHouseExit()->getHouseID();
304 }
305 }
306
307 bool only_colors = options.show_as_minimap || options.show_only_colors;
308
309 // Enable texture mode
310 if (!only_colors) {
311 glEnable(GL_TEXTURE_2D);
312 }
313
314 for (int map_z = start_z; map_z >= superend_z; map_z--) {
315 if (map_z == end_z && start_z != end_z && options.show_shade) {
316 // Draw shade
317 if (!only_colors) {
318 glDisable(GL_TEXTURE_2D);
319 }
320
321 glColor4ub(0, 0, 0, 128);
322 glBegin(GL_QUADS);
323 glVertex2f(0, int(screensize_y * zoom));
324 glVertex2f(int(screensize_x * zoom), int(screensize_y * zoom));
325 glVertex2f(int(screensize_x * zoom), 0);
326 glVertex2f(0, 0);
327 glEnd();
328
329 if (!only_colors) {
330 glEnable(GL_TEXTURE_2D);
331 }
332 }
333
334 if (map_z >= end_z) {
335 int nd_start_x = start_x & ~3;
336 int nd_start_y = start_y & ~3;
337 int nd_end_x = (end_x & ~3) + 4;
338 int nd_end_y = (end_y & ~3) + 4;
339
340 zoneTiles.clear();
341 for (int nd_map_x = nd_start_x; nd_map_x <= nd_end_x; nd_map_x += 4) {
342 for (int nd_map_y = nd_start_y; nd_map_y <= nd_end_y; nd_map_y += 4) {
343 QTreeNode* nd = editor.map.getLeaf(nd_map_x, nd_map_y);
344 if (!nd) {
345 if (live_client) {
346 nd = editor.map.createLeaf(nd_map_x, nd_map_y);
347 nd->setVisible(false, false);
348 } else {
349 continue;
350 }
351 }
352
353 if (!live_client || nd->isVisible(map_z > GROUND_LAYER)) {
354 for (int map_x = 0; map_x < 4; ++map_x) {
355 for (int map_y = 0; map_y < 4; ++map_y) {
356 TileLocation* location = nd->getTile(map_x, map_y, map_z);
357 DrawTile(location);
358 // draw light, but only if not zoomed too far
359 if (location && options.isDrawLight() && zoom <= 10.0) {
360 AddLight(location);
361 }
362 }
363 }
364 } else {
365 if (!nd->isRequested(map_z > GROUND_LAYER)) {
366 // Request the node
367 editor.QueryNode(nd_map_x, nd_map_y, map_z > GROUND_LAYER);
368 nd->setRequested(map_z > GROUND_LAYER, true);
369 }
370 int cy = (nd_map_y)*TileSize - view_scroll_y - getFloorAdjustment(floor);
371 int cx = (nd_map_x)*TileSize - view_scroll_x - getFloorAdjustment(floor);
372
373 glColor4ub(255, 0, 255, 128);
374 glBegin(GL_QUADS);
375 glVertex2f(cx, cy + TileSize * 4);
376 glVertex2f(cx + TileSize * 4, cy + TileSize * 4);
377 glVertex2f(cx + TileSize * 4, cy);
378 glVertex2f(cx, cy);
379 glEnd();
380 }
381 }
382 }
383 for (auto& itZonePos : zoneTiles) {
384 ZoneFinder finder(itZonePos.second);
385 auto zones = finder.findZones();
386
387 for (const auto& itZone : zones) {
388 const FinderPosition center = finder.findClosestToCenter(itZone);
389
390 QTreeNode* nd = editor.getMap().getLeaf(center.x, center.y);
391 TileLocation* location = nd->getTile(center.x, center.y, center.z);
392
393 const Tile* tile = location->get();
394
395 std::ostringstream tooltip;
396 tooltip << "zone id: ";
397 size_t zones = tile->getZoneIds().size();
398 for (const auto& zoneId : tile->getZoneIds()) {
399 tooltip << zoneId;
400 if (--zones > 0) {
401 tooltip << "/";
402 }
403 }
404
405 int offset;
406 if (map_z <= GROUND_LAYER) {
407 offset = (GROUND_LAYER - map_z) * TileSize;
408 }
409 else {
410 offset = TileSize * (floor - map_z);
411 }
412
413 int draw_x = ((tile->getX() * TileSize) - view_scroll_x) - offset;
414 int draw_y = ((tile->getY() * TileSize) - view_scroll_y) - offset;
415 MakeTooltip(draw_x, draw_y + 8, tooltip.str());
416 }
417 }
418 }
419
420 if (only_colors) {
421 glEnable(GL_TEXTURE_2D);
422 }
423
424 // Draws the doodad preview or the paste preview (or import preview)
425 if (g_gui.secondary_map != nullptr && !options.ingame) {
426 Position normalPos;
427 Position to(mouse_map_x, mouse_map_y, floor);
428
429 if (canvas->isPasting()) {
430 normalPos = editor.copybuffer.getPosition();
431 } else if (brush && brush->isDoodad()) {
432 normalPos = Position(0x8000, 0x8000, 0x8);
433 }
434
435 for (int map_x = start_x; map_x <= end_x; map_x++) {
436 for (int map_y = start_y; map_y <= end_y; map_y++) {
437 Position final(map_x, map_y, map_z);
438 Position pos = normalPos + final - to;
439 // Position pos = topos + copypos - Position(map_x, map_y, map_z);
440 if (pos.z >= MAP_LAYERS || pos.z < 0) {
441 continue;
442 }
443
444 Tile* tile = g_gui.secondary_map->getTile(pos);
445 if (tile) {
446 // Compensate for underground/overground
447 int offset;
448 if (map_z <= GROUND_LAYER) {
449 offset = (GROUND_LAYER - map_z) * TileSize;
450 } else {
451 offset = TileSize * (floor - map_z);
452 }
453
454 int draw_x = ((map_x * TileSize) - view_scroll_x) - offset;
455 int draw_y = ((map_y * TileSize) - view_scroll_y) - offset;
456
457 // Draw ground
458 uint8_t r = 160, g = 160, b = 160;
459 if (tile->ground) {
460 if (tile->isBlocking() && options.show_blocking) {
461 g = g / 3 * 2;
462 b = b / 3 * 2;
463 }
464 if (tile->isHouseTile() && options.show_houses) {
465 if ((int)tile->getHouseID() == current_house_id) {
466 r /= 2;
467 } else {
468 r /= 2;
469 g /= 2;
470 }
471 } else if (options.show_special_tiles && tile->isPZ()) {
472 r /= 2;
473 b /= 2;
474 }
475 if (options.show_special_tiles && tile->getMapFlags() & TILESTATE_PVPZONE) {
476 r = r / 3 * 2;
477 b = r / 3 * 2;
478 }
479 if (options.show_special_tiles && tile->getMapFlags() & TILESTATE_NOLOGOUT) {
480 b /= 2;
481 }
482 if (options.show_special_tiles && tile->getMapFlags() & TILESTATE_NOPVP) {
483 g /= 2;
484 }
485 if (options.show_zone_areas && tile->getMapFlags() & TILESTATE_ZONE_BRUSH) {
486 size_t zones = tile->getZoneIds().size();
487 uint16_t r16 = 0, g16 = 0, b16 = 0;
488 for (const auto& zoneId : tile->getZoneIds()) {
489 const uint16_t colorIndex = zoneId % colors.size();
490 const Color colour = colors.at(colorIndex);
491
492 r16 += std::get<0>(colour);
493 g16 += std::get<1>(colour);
494 b16 += std::get<2>(colour);
495 }
496
497 r = r16 / zones;
498 g = g16 / zones;
499 b = b16 / zones;
500 }
501 BlitItem(draw_x, draw_y, tile, tile->ground, true, r, g, b, 160);
502 }
503
504 // Draw items on the tile
505 if (zoom <= 10.0 || !options.hide_items_when_zoomed) {
506 ItemVector::iterator it;
507 for (it = tile->items.begin(); it != tile->items.end(); it++) {
508 if ((*it)->isBorder()) {
509 BlitItem(draw_x, draw_y, tile, *it, true, 160, r, g, b);
510 } else {
511 BlitItem(draw_x, draw_y, tile, *it, true, 160, 160, 160, 160);
512 }
513 }
514 if (tile->creature && options.show_creatures) {
515 BlitCreature(draw_x, draw_y, tile->creature);
516 }
517 }
518 }
519 }
520 }
521 }
522
523 --start_x;
524 --start_y;
525 ++end_x;
526 ++end_y;
527 }
528
529 if (!only_colors) {
530 glEnable(GL_TEXTURE_2D);
531 }
532}
533
534void MapDrawer::DrawIngameBox() {
535 int center_x = start_x + int(screensize_x * zoom / 64);
536 int center_y = start_y + int(screensize_y * zoom / 64);
537
538 int offset_y = 2;
539 int box_start_map_x = center_x;
540 int box_start_map_y = center_y + offset_y;
541 int box_end_map_x = center_x + ClientMapWidth;
542 int box_end_map_y = center_y + ClientMapHeight + offset_y;
543
544 int box_start_x = box_start_map_x * TileSize - view_scroll_x;
545 int box_start_y = box_start_map_y * TileSize - view_scroll_y;
546 int box_end_x = box_end_map_x * TileSize - view_scroll_x;
547 int box_end_y = box_end_map_y * TileSize - view_scroll_y;
548
549 static wxColor side_color(0, 0, 0, 200);
550
551 glDisable(GL_TEXTURE_2D);
552
553 // left side
554 if (box_start_map_x >= start_x) {
555 drawFilledRect(0, 0, box_start_x, screensize_y * zoom, side_color);
556 }
557
558 // right side
559 if (box_end_map_x < end_x) {
560 drawFilledRect(box_end_x, 0, screensize_x * zoom, screensize_y * zoom, side_color);
561 }
562
563 // top side
564 if (box_start_map_y >= start_y) {
565 drawFilledRect(box_start_x, 0, box_end_x - box_start_x, box_start_y, side_color);
566 }
567
568 // bottom side
569 if (box_end_map_y < end_y) {
570 drawFilledRect(box_start_x, box_end_y, box_end_x - box_start_x, screensize_y * zoom, side_color);
571 }
572
573 // hidden tiles
574 drawRect(box_start_x, box_start_y, box_end_x - box_start_x, box_end_y - box_start_y, *wxRED);
575
576 // visible tiles
577 box_start_x += TileSize;
578 box_start_y += TileSize;
579 box_end_x -= 1 * TileSize;
580 box_end_y -= 1 * TileSize;
581 drawRect(box_start_x, box_start_y, box_end_x - box_start_x, box_end_y - box_start_y, *wxGREEN);
582
583 // player position
584 box_start_x += (ClientMapWidth - 3) / 2 * TileSize;
585 box_start_y += (ClientMapHeight - 3) / 2 * TileSize;
586 box_end_x = box_start_x + TileSize;
587 box_end_y = box_start_y + TileSize;
588 drawRect(box_start_x, box_start_y, box_end_x - box_start_x, box_end_y - box_start_y, *wxGREEN);
589
590 glEnable(GL_TEXTURE_2D);
591}
592
593void MapDrawer::DrawGrid() {
594 if (zoom > g_settings.getInteger(Config::GRID_ZOOM_THRESHOLD)) {
595 return;
596 }
597
598 for (int y = start_y; y < end_y; ++y) {
599 glColor4ub(255, 255, 255, 128);
600 glBegin(GL_LINES);
601 glVertex2f(start_x * TileSize - view_scroll_x, y * TileSize - view_scroll_y);
602 glVertex2f(end_x * TileSize - view_scroll_x, y * TileSize - view_scroll_y);
603 glEnd();
604 }
605
606 for (int x = start_x; x < end_x; ++x) {
607 glColor4ub(255, 255, 255, 128);
608 glBegin(GL_LINES);
609 glVertex2f(x * TileSize - view_scroll_x, start_y * TileSize - view_scroll_y);
610 glVertex2f(x * TileSize - view_scroll_x, end_y * TileSize - view_scroll_y);
611 glEnd();
612 }
613}
614
615void MapDrawer::DrawDraggingShadow() {
616 glEnable(GL_TEXTURE_2D);
617
618 // Draw dragging shadow
619 if (!editor.selection.isBusy() && dragging && !options.ingame) {
620 for (TileSet::iterator tit = editor.selection.begin(); tit != editor.selection.end(); tit++) {
621 Tile* tile = *tit;
622 Position pos = tile->getPosition();
623
624 int move_x, move_y, move_z;
625 move_x = canvas->drag_start_x - mouse_map_x;
626 move_y = canvas->drag_start_y - mouse_map_y;
627 move_z = canvas->drag_start_z - floor;
628
629 pos.x -= move_x;
630 pos.y -= move_y;
631 pos.z -= move_z;
632
633 if (pos.z < 0 || pos.z >= MAP_LAYERS) {
634 continue;
635 }
636
637 // On screen and dragging?
638 if (pos.x + 2 > start_x && pos.x < end_x && pos.y + 2 > start_y && pos.y < end_y && (move_x != 0 || move_y != 0 || move_z != 0)) {
639 int offset;
640 if (pos.z <= GROUND_LAYER) {
641 offset = (GROUND_LAYER - pos.z) * TileSize;
642 } else {
643 offset = TileSize * (floor - pos.z);
644 }
645
646 int draw_x = ((pos.x * TileSize) - view_scroll_x) - offset;
647 int draw_y = ((pos.y * TileSize) - view_scroll_y) - offset;
648
649 // save performance when moving large chunks unzoomed
650 ItemVector toRender = tile->getSelectedItems(zoom > 3.0);
651 Tile* desttile = editor.map.getTile(pos);
652 for (ItemVector::const_iterator iit = toRender.begin(); iit != toRender.end(); iit++) {
653 if (desttile) {
654 BlitItem(draw_x, draw_y, desttile, *iit, true, 160, 160, 160, 160);
655 } else {
656 BlitItem(draw_x, draw_y, pos, *iit, true, 160, 160, 160, 160);
657 }
658 }
659
660 // save performance when moving large chunks unzoomed
661 if (zoom <= 3.0) {
662 if (tile->creature && tile->creature->isSelected() && options.show_creatures) {
663 BlitCreature(draw_x, draw_y, tile->creature);
664 }
665 if (tile->spawn && tile->spawn->isSelected()) {
666 BlitSpriteType(draw_x, draw_y, SPRITE_SPAWN, 160, 160, 160, 160);
667 }
668 }
669 }
670 }
671 }
672
673 glDisable(GL_TEXTURE_2D);
674}
675
676void MapDrawer::DrawHigherFloors() {
677 glEnable(GL_TEXTURE_2D);
678
679 // Draw "transparent higher floor"
680 if (floor != 8 && floor != 0 && options.transparent_floors) {
681 int map_z = floor - 1;
682 for (int map_x = start_x; map_x <= end_x; map_x++) {
683 for (int map_y = start_y; map_y <= end_y; map_y++) {
684 Tile* tile = editor.map.getTile(map_x, map_y, map_z);
685 if (tile) {
686 int offset;
687 if (map_z <= GROUND_LAYER) {
688 offset = (GROUND_LAYER - map_z) * TileSize;
689 } else {
690 offset = TileSize * (floor - map_z);
691 }
692
693 int draw_x = ((map_x * TileSize) - view_scroll_x) - offset;
694 int draw_y = ((map_y * TileSize) - view_scroll_y) - offset;
695
696 // Position pos = tile->getPosition();
697
698 if (tile->ground) {
699 if (tile->isPZ()) {
700 BlitItem(draw_x, draw_y, tile, tile->ground, false, 128, 255, 128, 96);
701 } else {
702 BlitItem(draw_x, draw_y, tile, tile->ground, false, 255, 255, 255, 96);
703 }
704 }
705 if (zoom <= g_settings.getInteger(Config::ITEM_DISPLAY_ZOOM_THRESHOLD) || !options.hide_items_when_zoomed) {
706 ItemVector::iterator it;
707 for (it = tile->items.begin(); it != tile->items.end(); it++) {
708 BlitItem(draw_x, draw_y, tile, *it, false, 255, 255, 255, 96);
709 }
710 }
711 }
712 }
713 }
714 }
715
716 glDisable(GL_TEXTURE_2D);
717}
718
719void MapDrawer::DrawSelectionBox() {
720 if (options.ingame) {
721 return;
722 }
723
724 // Draw bounding box
725
726 int last_click_rx = canvas->last_click_abs_x - view_scroll_x;
727 int last_click_ry = canvas->last_click_abs_y - view_scroll_y;
728 double cursor_rx = canvas->cursor_x * zoom;
729 double cursor_ry = canvas->cursor_y * zoom;
730
731 double lines[4][4];
732
733 lines[0][0] = last_click_rx;
734 lines[0][1] = last_click_ry;
735 lines[0][2] = cursor_rx;
736 lines[0][3] = last_click_ry;
737
738 lines[1][0] = cursor_rx;
739 lines[1][1] = last_click_ry;
740 lines[1][2] = cursor_rx;
741 lines[1][3] = cursor_ry;
742
743 lines[2][0] = cursor_rx;
744 lines[2][1] = cursor_ry;
745 lines[2][2] = last_click_rx;
746 lines[2][3] = cursor_ry;
747
748 lines[3][0] = last_click_rx;
749 lines[3][1] = cursor_ry;
750 lines[3][2] = last_click_rx;
751 lines[3][3] = last_click_ry;
752
753 glEnable(GL_LINE_STIPPLE);
754 glLineStipple(1, 0xf0);
755 glLineWidth(1.0);
756 glColor4f(1.0, 1.0, 1.0, 1.0);
757 glBegin(GL_LINES);
758 for (int i = 0; i < 4; i++) {
759 glVertex2f(lines[i][0], lines[i][1]);
760 glVertex2f(lines[i][2], lines[i][3]);
761 }
762 glEnd();
763 glDisable(GL_LINE_STIPPLE);
764}
765
766void MapDrawer::DrawLiveCursors() {
767 if (options.ingame || !editor.IsLive()) {
768 return;
769 }
770
771 LiveSocket& live = editor.GetLive();
772 for (LiveCursor& cursor : live.getCursorList()) {
773 if (cursor.pos.z <= GROUND_LAYER && floor > GROUND_LAYER) {
774 continue;
775 }
776
777 if (cursor.pos.z > GROUND_LAYER && floor <= 8) {
778 continue;
779 }
780
781 // Get the cursor position and color
782 wxColor cursorColor = cursor.color;
783
784 // Fade the color if cursor is on a lower floor
785 if (cursor.pos.z < floor) {
786 cursorColor = wxColor(
787 cursorColor.Red(),
788 cursorColor.Green(),
789 cursorColor.Blue(),
790 std::max<uint8_t>(cursorColor.Alpha() / 2, 64)
791 );
792 }
793
794 int offset;
795 if (cursor.pos.z <= GROUND_LAYER) {
796 offset = (GROUND_LAYER - cursor.pos.z) * TileSize;
797 } else {
798 offset = TileSize * (floor - cursor.pos.z);
799 }
800
801 // Draw a brush highlight around the cursor
802 const int brushSize = 1; // Size of brush (1 = 3x3 tiles, 2 = 5x5 tiles, etc.)
803
804 for (int y = -brushSize; y <= brushSize; y++) {
805 for (int x = -brushSize; x <= brushSize; x++) {
806 // Calculate the display position for each tile in the brush
807 float draw_x = (((cursor.pos.x + x) * TileSize) - view_scroll_x) - offset;
808 float draw_y = (((cursor.pos.y + y) * TileSize) - view_scroll_y) - offset;
809
810 // Choose how to display the brush: as square or circle
811 // Use a circular brush (similar to the regular drawing brush)
812 if (brushSize > 0) {
813 // For circular brush, calculate distance from center
814 float distance = sqrt((float)(x * x) + (float)(y * y));
815 if (distance > brushSize + 0.005f) {
816 continue; // Skip tiles outside the circle
817 }
818 }
819
820 // Draw the tile with the user's color
821 glColor4ub(
822 cursorColor.Red(),
823 cursorColor.Green(),
824 cursorColor.Blue(),
825 std::min<uint8_t>(cursorColor.Alpha(), 120) // Lower alpha for brush highlight
826 );
827
828 glBegin(GL_QUADS);
829 glVertex2f(draw_x, draw_y + TileSize);
830 glVertex2f(draw_x + TileSize, draw_y + TileSize);
831 glVertex2f(draw_x + TileSize, draw_y);
832 glVertex2f(draw_x, draw_y);
833 glEnd();
834 }
835 }
836
837 // Draw a brighter center tile to mark the actual cursor position
838 float center_x = ((cursor.pos.x * TileSize) - view_scroll_x) - offset;
839 float center_y = ((cursor.pos.y * TileSize) - view_scroll_y) - offset;
840
841 glColor(cursorColor); // Original color for the center
842 glBegin(GL_QUADS);
843 glVertex2f(center_x, center_y + TileSize);
844 glVertex2f(center_x + TileSize, center_y + TileSize);
845 glVertex2f(center_x + TileSize, center_y);
846 glVertex2f(center_x, center_y);
847 glEnd();
848
849 // Draw a small username indicator above the brush
850 // This would require additional code to render text in OpenGL, which is beyond the scope of this edit
851 }
852}
853
854void MapDrawer::DrawBrush() {
855 if (!g_gui.IsDrawingMode()) {
856 return;
857 }
858 if (!g_gui.GetCurrentBrush()) {
859 return;
860 }
861 if (options.ingame) {
862 return;
863 }
864
865 Brush* brush = g_gui.GetCurrentBrush();
866
867 BrushColor brushColor = COLOR_BLANK;
868 if (brush->isTerrain() || brush->isTable() || brush->isCarpet()) {
869 brushColor = COLOR_BRUSH;
870 } else if (brush->isHouse()) {
871 brushColor = COLOR_HOUSE_BRUSH;
872 } else if (brush->isFlag()) {
873 brushColor = COLOR_FLAG_BRUSH;
874 } else if (brush->isSpawn()) {
875 brushColor = COLOR_SPAWN_BRUSH;
876 } else if (brush->isEraser()) {
877 brushColor = COLOR_ERASER;
878 }
879
880 if (dragging_draw) {
881 ASSERT(brush->canDrag());
882
883 if (brush->isWall()) {
884 int last_click_start_map_x = std::min(canvas->last_click_map_x, mouse_map_x);
885 int last_click_start_map_y = std::min(canvas->last_click_map_y, mouse_map_y);
886 int last_click_end_map_x = std::max(canvas->last_click_map_x, mouse_map_x) + 1;
887 int last_click_end_map_y = std::max(canvas->last_click_map_y, mouse_map_y) + 1;
888
889 int last_click_start_sx = last_click_start_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
890 int last_click_start_sy = last_click_start_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
891 int last_click_end_sx = last_click_end_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
892 int last_click_end_sy = last_click_end_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
893
894 int delta_x = last_click_end_sx - last_click_start_sx;
895 int delta_y = last_click_end_sy - last_click_start_sy;
896
897 glColor(brushColor);
898 glBegin(GL_QUADS);
899 {
900 glVertex2f(last_click_start_sx, last_click_start_sy + TileSize);
901 glVertex2f(last_click_end_sx, last_click_start_sy + TileSize);
902 glVertex2f(last_click_end_sx, last_click_start_sy);
903 glVertex2f(last_click_start_sx, last_click_start_sy);
904 }
905
906 if (delta_y > TileSize) {
907 glVertex2f(last_click_start_sx, last_click_end_sy - TileSize);
908 glVertex2f(last_click_start_sx + TileSize, last_click_end_sy - TileSize);
909 glVertex2f(last_click_start_sx + TileSize, last_click_start_sy + TileSize);
910 glVertex2f(last_click_start_sx, last_click_start_sy + TileSize);
911 }
912
913 if (delta_x > TileSize && delta_y > TileSize) {
914 glVertex2f(last_click_end_sx - TileSize, last_click_start_sy + TileSize);
915 glVertex2f(last_click_end_sx, last_click_start_sy + TileSize);
916 glVertex2f(last_click_end_sx, last_click_end_sy - TileSize);
917 glVertex2f(last_click_end_sx - TileSize, last_click_end_sy - TileSize);
918 }
919
920 if (delta_y > TileSize) {
921 glVertex2f(last_click_start_sx, last_click_end_sy - TileSize);
922 glVertex2f(last_click_end_sx, last_click_end_sy - TileSize);
923 glVertex2f(last_click_end_sx, last_click_end_sy);
924 glVertex2f(last_click_start_sx, last_click_end_sy);
925 }
926 glEnd();
927 } else {
928 if (brush->isRaw()) {
929 glEnable(GL_TEXTURE_2D);
930 }
931
932 if (g_gui.GetBrushShape() == BRUSHSHAPE_SQUARE || brush->isSpawn() /* Spawn brush is always square */) {
933 if (brush->isRaw() || brush->isOptionalBorder()) {
934 int start_x, end_x;
935 int start_y, end_y;
936
937 if (mouse_map_x < canvas->last_click_map_x) {
938 start_x = mouse_map_x;
939 end_x = canvas->last_click_map_x;
940 } else {
941 start_x = canvas->last_click_map_x;
942 end_x = mouse_map_x;
943 }
944 if (mouse_map_y < canvas->last_click_map_y) {
945 start_y = mouse_map_y;
946 end_y = canvas->last_click_map_y;
947 } else {
948 start_y = canvas->last_click_map_y;
949 end_y = mouse_map_y;
950 }
951
952 RAWBrush* raw_brush = nullptr;
953 if (brush->isRaw()) {
954 raw_brush = brush->asRaw();
955 }
956
957 for (int y = start_y; y <= end_y; y++) {
958 int cy = y * TileSize - view_scroll_y - getFloorAdjustment(floor);
959 for (int x = start_x; x <= end_x; x++) {
960 int cx = x * TileSize - view_scroll_x - getFloorAdjustment(floor);
961 if (brush->isOptionalBorder()) {
962 glColorCheck(brush, Position(x, y, floor));
963 } else {
964 DrawRawBrush(cx, cy, raw_brush->getItemType(), 160, 160, 160, 160);
965 }
966 }
967 }
968 } else {
969 int last_click_start_map_x = std::min(canvas->last_click_map_x, mouse_map_x);
970 int last_click_start_map_y = std::min(canvas->last_click_map_y, mouse_map_y);
971 int last_click_end_map_x = std::max(canvas->last_click_map_x, mouse_map_x) + 1;
972 int last_click_end_map_y = std::max(canvas->last_click_map_y, mouse_map_y) + 1;
973
974 int last_click_start_sx = last_click_start_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
975 int last_click_start_sy = last_click_start_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
976 int last_click_end_sx = last_click_end_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
977 int last_click_end_sy = last_click_end_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
978
979 glColor(brushColor);
980 glBegin(GL_QUADS);
981 glVertex2f(last_click_start_sx, last_click_start_sy);
982 glVertex2f(last_click_end_sx, last_click_start_sy);
983 glVertex2f(last_click_end_sx, last_click_end_sy);
984 glVertex2f(last_click_start_sx, last_click_end_sy);
985 glEnd();
986 }
987 } else if (g_gui.GetBrushShape() == BRUSHSHAPE_CIRCLE) {
988 // Calculate drawing offsets
989 int start_x, end_x;
990 int start_y, end_y;
991 int width = std::max(
992 std::abs(std::max(mouse_map_y, canvas->last_click_map_y) - std::min(mouse_map_y, canvas->last_click_map_y)),
993 std::abs(std::max(mouse_map_x, canvas->last_click_map_x) - std::min(mouse_map_x, canvas->last_click_map_x))
994 );
995
996 if (mouse_map_x < canvas->last_click_map_x) {
997 start_x = canvas->last_click_map_x - width;
998 end_x = canvas->last_click_map_x;
999 } else {
1000 start_x = canvas->last_click_map_x;
1001 end_x = canvas->last_click_map_x + width;
1002 }
1003
1004 if (mouse_map_y < canvas->last_click_map_y) {
1005 start_y = canvas->last_click_map_y - width;
1006 end_y = canvas->last_click_map_y;
1007 } else {
1008 start_y = canvas->last_click_map_y;
1009 end_y = canvas->last_click_map_y + width;
1010 }
1011
1012 int center_x = start_x + (end_x - start_x) / 2;
1013 int center_y = start_y + (end_y - start_y) / 2;
1014 float radii = width / 2.0f + 0.005f;
1015
1016 RAWBrush* raw_brush = nullptr;
1017 if (brush->isRaw()) {
1018 raw_brush = brush->asRaw();
1019 }
1020
1021 for (int y = start_y - 1; y <= end_y + 1; y++) {
1022 int cy = y * TileSize - view_scroll_y - getFloorAdjustment(floor);
1023 float dy = center_y - y;
1024 for (int x = start_x - 1; x <= end_x + 1; x++) {
1025 int cx = x * TileSize - view_scroll_x - getFloorAdjustment(floor);
1026
1027 float dx = center_x - x;
1028 // printf("%f;%f\n", dx, dy);
1029 float distance = sqrt(dx * dx + dy * dy);
1030 if (distance < radii) {
1031 if (brush->isRaw()) {
1032 DrawRawBrush(cx, cy, raw_brush->getItemType(), 160, 160, 160, 160);
1033 } else {
1034 glColor(brushColor);
1035 glBegin(GL_QUADS);
1036 glVertex2f(cx, cy + TileSize);
1037 glVertex2f(cx + TileSize, cy + TileSize);
1038 glVertex2f(cx + TileSize, cy);
1039 glVertex2f(cx, cy);
1040 glEnd();
1041 }
1042 }
1043 }
1044 }
1045 }
1046
1047 if (brush->isRaw()) {
1048 glDisable(GL_TEXTURE_2D);
1049 }
1050 }
1051 } else {
1052 if (brush->isWall()) {
1053 int start_map_x = mouse_map_x - g_gui.GetBrushSize();
1054 int start_map_y = mouse_map_y - g_gui.GetBrushSize();
1055 int end_map_x = mouse_map_x + g_gui.GetBrushSize() + 1;
1056 int end_map_y = mouse_map_y + g_gui.GetBrushSize() + 1;
1057
1058 int start_sx = start_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
1059 int start_sy = start_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
1060 int end_sx = end_map_x * TileSize - view_scroll_x - getFloorAdjustment(floor);
1061 int end_sy = end_map_y * TileSize - view_scroll_y - getFloorAdjustment(floor);
1062
1063 int delta_x = end_sx - start_sx;
1064 int delta_y = end_sy - start_sy;
1065
1066 glColor(brushColor);
1067 glBegin(GL_QUADS);
1068 {
1069 glVertex2f(start_sx, start_sy + TileSize);
1070 glVertex2f(end_sx, start_sy + TileSize);
1071 glVertex2f(end_sx, start_sy);
1072 glVertex2f(start_sx, start_sy);
1073 }
1074
1075 if (delta_y > TileSize) {
1076 glVertex2f(start_sx, end_sy - TileSize);
1077 glVertex2f(start_sx + TileSize, end_sy - TileSize);
1078 glVertex2f(start_sx + TileSize, start_sy + TileSize);
1079 glVertex2f(start_sx, start_sy + TileSize);
1080 }
1081
1082 if (delta_x > TileSize && delta_y > TileSize) {
1083 glVertex2f(end_sx - TileSize, start_sy + TileSize);
1084 glVertex2f(end_sx, start_sy + TileSize);
1085 glVertex2f(end_sx, end_sy - TileSize);
1086 glVertex2f(end_sx - TileSize, end_sy - TileSize);
1087 }
1088
1089 if (delta_y > TileSize) {
1090 glVertex2f(start_sx, end_sy - TileSize);
1091 glVertex2f(end_sx, end_sy - TileSize);
1092 glVertex2f(end_sx, end_sy);
1093 glVertex2f(start_sx, end_sy);
1094 }
1095 glEnd();
1096 } else if (brush->isDoor()) {
1097 int cx = (mouse_map_x)*TileSize - view_scroll_x - getFloorAdjustment(floor);
1098 int cy = (mouse_map_y)*TileSize - view_scroll_y - getFloorAdjustment(floor);
1099
1100 glColorCheck(brush, Position(mouse_map_x, mouse_map_y, floor));
1101 glBegin(GL_QUADS);
1102 glVertex2f(cx, cy + TileSize);
1103 glVertex2f(cx + TileSize, cy + TileSize);
1104 glVertex2f(cx + TileSize, cy);
1105 glVertex2f(cx, cy);
1106 glEnd();
1107 } else if (brush->isCreature()) {
1108 glEnable(GL_TEXTURE_2D);
1109 int cy = (mouse_map_y)*TileSize - view_scroll_y - getFloorAdjustment(floor);
1110 int cx = (mouse_map_x)*TileSize - view_scroll_x - getFloorAdjustment(floor);
1111 CreatureBrush* creature_brush = brush->asCreature();
1112 if (creature_brush->canDraw(&editor.map, Position(mouse_map_x, mouse_map_y, floor))) {
1113 BlitCreature(cx, cy, creature_brush->getType()->outfit, SOUTH, 255, 255, 255, 160);
1114 } else {
1115 BlitCreature(cx, cy, creature_brush->getType()->outfit, SOUTH, 255, 64, 64, 160);
1116 }
1117 glDisable(GL_TEXTURE_2D);
1118 } else if (!brush->isDoodad()) {
1119 RAWBrush* raw_brush = nullptr;
1120 if (brush->isRaw()) { // Textured brush
1121 glEnable(GL_TEXTURE_2D);
1122 raw_brush = brush->asRaw();
1123 }
1124
1125 for (int y = -g_gui.GetBrushSize() - 1; y <= g_gui.GetBrushSize() + 1; y++) {
1126 int cy = (mouse_map_y + y) * TileSize - view_scroll_y - getFloorAdjustment(floor);
1127 for (int x = -g_gui.GetBrushSize() - 1; x <= g_gui.GetBrushSize() + 1; x++) {
1128 int cx = (mouse_map_x + x) * TileSize - view_scroll_x - getFloorAdjustment(floor);
1129 if (g_gui.GetBrushShape() == BRUSHSHAPE_SQUARE) {
1130 if (x >= -g_gui.GetBrushSize() && x <= g_gui.GetBrushSize() && y >= -g_gui.GetBrushSize() && y <= g_gui.GetBrushSize()) {
1131 if (brush->isRaw()) {
1132 DrawRawBrush(cx, cy, raw_brush->getItemType(), 160, 160, 160, 160);
1133 } else {
1134 if (brush->isWaypoint()) {
1135 uint8_t r, g, b;
1136 getColor(brush, Position(mouse_map_x + x, mouse_map_y + y, floor), r, g, b);
1137 DrawBrushIndicator(cx, cy, brush, r, g, b);
1138 } else {
1139 if (brush->isHouseExit() || brush->isOptionalBorder()) {
1140 glColorCheck(brush, Position(mouse_map_x + x, mouse_map_y + y, floor));
1141 } else {
1142 glColor(brushColor);
1143 }
1144
1145 glBegin(GL_QUADS);
1146 glVertex2f(cx, cy + TileSize);
1147 glVertex2f(cx + TileSize, cy + TileSize);
1148 glVertex2f(cx + TileSize, cy);
1149 glVertex2f(cx, cy);
1150 glEnd();
1151 }
1152 }
1153 }
1154 } else if (g_gui.GetBrushShape() == BRUSHSHAPE_CIRCLE) {
1155 double distance = sqrt(double(x * x) + double(y * y));
1156 if (distance < g_gui.GetBrushSize() + 0.005) {
1157 if (brush->isRaw()) {
1158 DrawRawBrush(cx, cy, raw_brush->getItemType(), 160, 160, 160, 160);
1159 } else {
1160 if (brush->isWaypoint()) {
1161 uint8_t r, g, b;
1162 getColor(brush, Position(mouse_map_x + x, mouse_map_y + y, floor), r, g, b);
1163 DrawBrushIndicator(cx, cy, brush, r, g, b);
1164 } else {
1165 if (brush->isHouseExit() || brush->isOptionalBorder()) {
1166 glColorCheck(brush, Position(mouse_map_x + x, mouse_map_y + y, floor));
1167 } else {
1168 glColor(brushColor);
1169 }
1170
1171 glBegin(GL_QUADS);
1172 glVertex2f(cx, cy + TileSize);
1173 glVertex2f(cx + TileSize, cy + TileSize);
1174 glVertex2f(cx + TileSize, cy);
1175 glVertex2f(cx, cy);
1176 glEnd();
1177 }
1178 }
1179 }
1180 }
1181 }
1182 }
1183
1184 if (brush->isRaw()) { // Textured brush
1185 glDisable(GL_TEXTURE_2D);
1186 }
1187 }
1188 }
1189}
1190
1191void MapDrawer::BlitItem(int& draw_x, int& draw_y, const Tile* tile, Item* item, bool ephemeral, int red, int green, int blue, int alpha) {
1192 const Position& pos = tile->getPosition();
1193 BlitItem(draw_x, draw_y, pos, item, ephemeral, red, green, blue, alpha, tile);
1194}
1195
1196void MapDrawer::BlitItem(int& draw_x, int& draw_y, const Position& pos, Item* item, bool ephemeral, int red, int green, int blue, int alpha, const Tile* tile) {
1197 ItemType& it = g_items[item->getID()];
1198
1199 // Locked door indicator
1200 if (!options.ingame && options.highlight_locked_doors && it.isDoor() && it.isLocked) {
1201 blue /= 2;
1202 green /= 2;
1203 }
1204
1205 if (!options.ingame && !ephemeral && item->isSelected()) {
1206 red /= 2;
1207 blue /= 2;
1208 green /= 2;
1209 }
1210
1211 // item sprite
1212 GameSprite* spr = it.sprite;
1213
1214 // Display invisible and invalid items
1215 // Ugly hacks. :)
1216 if (!options.ingame && options.show_tech_items) {
1217 // Red invalid client id
1218 if (it.id == 0) {
1219 BlitSquare(draw_x, draw_y, red, 0, 0, alpha);
1220 return;
1221 }
1222
1223 switch (it.clientID) {
1224 // Yellow invisible stairs tile (459)
1225 case 469:
1226 BlitSquare(draw_x, draw_y, red, green, 0, alpha / 3 * 2);
1227 return;
1228
1229 // Red invisible walkable tile (460)
1230 case 470:
1231 case 17970:
1232 case 20028:
1233 case 34168:
1234 BlitSquare(draw_x, draw_y, red, 0, 0, alpha / 3 * 2);
1235 return;
1236
1237 // Cyan invisible wall (1548)
1238 case 2187:
1239 BlitSquare(draw_x, draw_y, 0, green, blue, 80);
1240 return;
1241
1242 default:
1243 break;
1244 }
1245
1246 // primal light
1247 if (it.clientID >= 39092 && it.clientID <= 39100 || it.clientID == 39236 || it.clientID == 39367 || it.clientID == 39368) {
1248 spr = g_items[SPRITE_LIGHTSOURCE].sprite;
1249 red = 0;
1250 alpha = 180;
1251 }
1252 }
1253
1254 // metaItem, sprite not found or not hidden
1255 if (it.isMetaItem() || spr == nullptr || !ephemeral && it.pickupable && !options.show_items) {
1256 return;
1257 }
1258
1259 int screenx = draw_x - spr->getDrawOffset().first;
1260 int screeny = draw_y - spr->getDrawOffset().second;
1261
1262 // Set the newd drawing height accordingly
1263 draw_x -= spr->getDrawHeight();
1264 draw_y -= spr->getDrawHeight();
1265
1266 int subtype = -1;
1267
1268 int pattern_x = pos.x % spr->pattern_x;
1269 int pattern_y = pos.y % spr->pattern_y;
1270 int pattern_z = pos.z % spr->pattern_z;
1271
1272 if (it.isSplash() || it.isFluidContainer()) {
1273 subtype = item->getSubtype();
1274 } else if (it.isHangable) {
1275 if (tile && tile->hasProperty(HOOK_SOUTH)) {
1276 pattern_x = 1;
1277 } else if (tile && tile->hasProperty(HOOK_EAST)) {
1278 pattern_x = 2;
1279 } else {
1280 pattern_x = 0;
1281 }
1282 } else if (it.stackable) {
1283 if (item->getSubtype() <= 1) {
1284 subtype = 0;
1285 } else if (item->getSubtype() <= 2) {
1286 subtype = 1;
1287 } else if (item->getSubtype() <= 3) {
1288 subtype = 2;
1289 } else if (item->getSubtype() <= 4) {
1290 subtype = 3;
1291 } else if (item->getSubtype() < 10) {
1292 subtype = 4;
1293 } else if (item->getSubtype() < 25) {
1294 subtype = 5;
1295 } else if (item->getSubtype() < 50) {
1296 subtype = 6;
1297 } else {
1298 subtype = 7;
1299 }
1300 }
1301
1302 if (!ephemeral && options.transparent_items && (!it.isGroundTile() || spr->width > 1 || spr->height > 1) && !it.isSplash() && (!it.isBorder || spr->width > 1 || spr->height > 1)) {
1303 alpha /= 2;
1304 }
1305
1306 Podium* podium = dynamic_cast<Podium*>(item);
1307 if (it.isPodium() && !podium->hasShowPlatform() && !options.ingame) {
1308 if (options.show_tech_items) {
1309 alpha /= 2;
1310 } else {
1311 alpha = 0;
1312 }
1313 }
1314
1315 int frame = item->getFrame();
1316 for (int cx = 0; cx != spr->width; cx++) {
1317 for (int cy = 0; cy != spr->height; cy++) {
1318 for (int cf = 0; cf != spr->layers; cf++) {
1319 int texnum = spr->getHardwareID(cx, cy, cf, subtype, pattern_x, pattern_y, pattern_z, frame);
1320 glBlitTexture(screenx - cx * TileSize, screeny - cy * TileSize, texnum, red, green, blue, alpha);
1321 }
1322 }
1323 }
1324
1325 // zoomed out very far, avoid drawing stuff barely visible
1326 if (zoom > 3.0) {
1327 return;
1328 }
1329
1330 if (it.isPodium()) {
1331 Outfit outfit = podium->getOutfit();
1332 if (!podium->hasShowOutfit()) {
1333 if (podium->hasShowMount()) {
1334 outfit.lookType = outfit.lookMount;
1335 outfit.lookHead = outfit.lookMountHead;
1336 outfit.lookBody = outfit.lookMountBody;
1337 outfit.lookLegs = outfit.lookMountLegs;
1338 outfit.lookFeet = outfit.lookMountFeet;
1339 outfit.lookAddon = 0;
1340 outfit.lookMount = 0;
1341 } else {
1342 outfit.lookType = 0;
1343 }
1344 }
1345 if (!podium->hasShowMount()) {
1346 outfit.lookMount = 0;
1347 }
1348
1349 BlitCreature(draw_x, draw_y, outfit, static_cast<Direction>(podium->getDirection()), red, green, blue, 255);
1350 }
1351
1352 // draw wall hook
1353 if (!options.ingame && options.show_hooks && (it.hookSouth || it.hookEast)) {
1354 DrawHookIndicator(draw_x, draw_y, it);
1355 }
1356
1357 // draw light color indicator
1358 if (!options.ingame && options.show_light_str) {
1359 const SpriteLight& light = item->getLight();
1360 if (light.intensity > 0) {
1361 wxColor lightColor = colorFromEightBit(light.color);
1362 uint8_t byteR = lightColor.Red();
1363 uint8_t byteG = lightColor.Green();
1364 uint8_t byteB = lightColor.Blue();
1365 uint8_t byteA = 255;
1366
1367 int startOffset = std::max<int>(16, 32 - light.intensity);
1368 int sqSize = TileSize - startOffset;
1369 glDisable(GL_TEXTURE_2D);
1370 glBlitSquare(draw_x + startOffset - 2, draw_y + startOffset - 2, 0, 0, 0, byteA, sqSize + 2);
1371 glBlitSquare(draw_x + startOffset - 1, draw_y + startOffset - 1, byteR, byteG, byteB, byteA, sqSize);
1372 glEnable(GL_TEXTURE_2D);
1373 }
1374 }
1375}
1376
1377void MapDrawer::BlitSpriteType(int screenx, int screeny, uint32_t spriteid, int red, int green, int blue, int alpha) {
1378 GameSprite* spr = g_items[spriteid].sprite;
1379 if (spr == nullptr) {
1380 return;
1381 }
1382 screenx -= spr->getDrawOffset().first;
1383 screeny -= spr->getDrawOffset().second;
1384
1385 int tme = 0; // GetTime() % itype->FPA;
1386 for (int cx = 0; cx != spr->width; ++cx) {
1387 for (int cy = 0; cy != spr->height; ++cy) {
1388 for (int cf = 0; cf != spr->layers; ++cf) {
1389 int texnum = spr->getHardwareID(cx, cy, cf, -1, 0, 0, 0, tme);
1390 // printf("CF: %d\tTexturenum: %d\n", cf, texnum);
1391 glBlitTexture(screenx - cx * TileSize, screeny - cy * TileSize, texnum, red, green, blue, alpha);
1392 }
1393 }
1394 }
1395}
1396
1397void MapDrawer::BlitSpriteType(int screenx, int screeny, GameSprite* spr, int red, int green, int blue, int alpha) {
1398 if (spr == nullptr) {
1399 return;
1400 }
1401 screenx -= spr->getDrawOffset().first;
1402 screeny -= spr->getDrawOffset().second;
1403
1404 int tme = 0; // GetTime() % itype->FPA;
1405 for (int cx = 0; cx != spr->width; ++cx) {
1406 for (int cy = 0; cy != spr->height; ++cy) {
1407 for (int cf = 0; cf != spr->layers; ++cf) {
1408 int texnum = spr->getHardwareID(cx, cy, cf, -1, 0, 0, 0, tme);
1409 // printf("CF: %d\tTexturenum: %d\n", cf, texnum);
1410 glBlitTexture(screenx - cx * TileSize, screeny - cy * TileSize, texnum, red, green, blue, alpha);
1411 }
1412 }
1413 }
1414}
1415
1416void MapDrawer::BlitCreature(int screenx, int screeny, const Outfit& outfit, Direction dir, int red, int green, int blue, int alpha) {
1417 if (outfit.lookItem != 0) {
1418 ItemType& it = g_items[outfit.lookItem];
1419 BlitSpriteType(screenx, screeny, it.sprite, red, green, blue, alpha);
1420 } else {
1421 // get outfit sprite
1422 GameSprite* spr = g_gui.gfx.getCreatureSprite(outfit.lookType);
1423 if (!spr || outfit.lookType == 0) {
1424 return;
1425 }
1426
1427 int tme = 0; // GetTime() % itype->FPA;
1428
1429 // mount and addon drawing thanks to otc code
1430 // mount colors by Zbizu
1431 int pattern_z = 0;
1432 if (outfit.lookMount != 0) {
1433 if (GameSprite* mountSpr = g_gui.gfx.getCreatureSprite(outfit.lookMount)) {
1434 // generate mount colors
1435 Outfit mountOutfit;
1436 mountOutfit.lookType = outfit.lookMount;
1437 mountOutfit.lookHead = outfit.lookMountHead;
1438 mountOutfit.lookBody = outfit.lookMountBody;
1439 mountOutfit.lookLegs = outfit.lookMountLegs;
1440 mountOutfit.lookFeet = outfit.lookMountFeet;
1441
1442 for (int cx = 0; cx != mountSpr->width; ++cx) {
1443 for (int cy = 0; cy != mountSpr->height; ++cy) {
1444 int texnum = mountSpr->getHardwareID(cx, cy, (int)dir, 0, 0, mountOutfit, tme);
1445 glBlitTexture(screenx - cx * TileSize, screeny - cy * TileSize, texnum, red, green, blue, alpha);
1446 }
1447 }
1448
1449 pattern_z = std::min<int>(1, spr->pattern_z - 1);
1450 }
1451 }
1452
1453 // pattern_y => creature addon
1454 for (int pattern_y = 0; pattern_y < spr->pattern_y; pattern_y++) {
1455
1456 // continue if we dont have this addon
1457 if (pattern_y > 0 && !(outfit.lookAddon & (1 << (pattern_y - 1)))) {
1458 continue;
1459 }
1460
1461 for (int cx = 0; cx != spr->width; ++cx) {
1462 for (int cy = 0; cy != spr->height; ++cy) {
1463 int texnum = spr->getHardwareID(cx, cy, (int)dir, pattern_y, pattern_z, outfit, tme);
1464 glBlitTexture(screenx - cx * TileSize, screeny - cy * TileSize, texnum, red, green, blue, alpha);
1465 }
1466 }
1467 }
1468 }
1469}
1470
1471void MapDrawer::BlitCreature(int screenx, int screeny, const Creature* c, int red, int green, int blue, int alpha) {
1472 if (!options.ingame && c->isSelected()) {
1473 red /= 2;
1474 green /= 2;
1475 blue /= 2;
1476 }
1477 BlitCreature(screenx, screeny, c->getLookType(), c->getDirection(), red, green, blue, alpha);
1478}
1479
1480void MapDrawer::BlitSquare(int sx, int sy, int red, int green, int blue, int alpha, int size) {
1481 if (size == 0) {
1482 size = TileSize;
1483 }
1484
1485 GameSprite* spr = g_items[SPRITE_ZONE].sprite;
1486 if (!spr) {
1487 return;
1488 }
1489
1490 int texnum = spr->getHardwareID(0, 0, 0, -1, 0, 0, 0, 0);
1491 if (texnum == 0) {
1492 return;
1493 }
1494
1495 glBindTexture(GL_TEXTURE_2D, texnum);
1496 glColor4ub(uint8_t(red), uint8_t(green), uint8_t(blue), uint8_t(alpha));
1497 glBegin(GL_QUADS);
1498 glTexCoord2f(0.f, 0.f);
1499 glVertex2f(sx, sy);
1500 glTexCoord2f(1.f, 0.f);
1501 glVertex2f(sx + TileSize, sy);
1502 glTexCoord2f(1.f, 1.f);
1503 glVertex2f(sx + TileSize, sy + TileSize);
1504 glTexCoord2f(0.f, 1.f);
1505 glVertex2f(sx, sy + TileSize);
1506 glEnd();
1507}
1508
1509void MapDrawer::DrawRawBrush(int screenx, int screeny, ItemType* itemType, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha) {
1510 GameSprite* spr = itemType->sprite;
1511 uint16_t cid = itemType->clientID;
1512
1513 switch (cid) {
1514 // Yellow invisible stairs tile
1515 case 469:
1516 b = 0;
1517 alpha = alpha / 3 * 2;
1518 spr = g_items[SPRITE_ZONE].sprite;
1519 break;
1520
1521 // Red invisible walkable tile
1522 case 470:
1523 g = 0;
1524 b = 0;
1525 alpha = alpha / 3 * 2;
1526 spr = g_items[SPRITE_ZONE].sprite;
1527 break;
1528
1529 // Cyan invisible wall
1530 case 2187:
1531 r = 0;
1532 alpha = alpha / 3;
1533 spr = g_items[SPRITE_ZONE].sprite;
1534 break;
1535
1536 default:
1537 break;
1538 }
1539
1540 // primal light
1541 if (cid >= 39092 && cid <= 39100 || cid == 39236 || cid == 39367 || cid == 39368) {
1542 spr = g_items[SPRITE_LIGHTSOURCE].sprite;
1543 r = 0;
1544 alpha = alpha / 3 * 2;
1545 }
1546
1547 BlitSpriteType(screenx, screeny, spr, r, g, b, alpha);
1548}
1549
1550void MapDrawer::WriteTooltip(Tile* tile, Item* item, std::ostringstream& stream, bool isHouseTile) {
1551 if (item == nullptr || zoom > g_settings.getInteger(Config::TOOLTIP_MAX_ZOOM)) {
1552 return;
1553 }
1554
1555 if (!item) return;
1556
1557 const uint16_t id = item->getID();
1558 if (id < 100) {
1559 return;
1560 }
1561
1562 const auto& zoneIds = tile->getZoneIds();
1563 const uint16_t unique = item->getUniqueID();
1564 const uint16_t action = item->getActionID();
1565 const std::string& text = item->getText();
1566 uint8_t doorId = 0;
1567
1568 if (isHouseTile && item->isDoor()) {
1569 if (Door* door = dynamic_cast<Door*>(item)) {
1570 if (door->isRealDoor()) {
1571 doorId = door->getDoorID();
1572 }
1573 }
1574 }
1575
1576 Teleport* tp = dynamic_cast<Teleport*>(item);
1577 if (unique == 0 && action == 0 && doorId == 0 && text.empty() && !tp && zoneIds.empty()) {
1578 return;
1579 }
1580
1581 if (stream.tellp() > 0) {
1582 stream << "\n";
1583 }
1584
1585 if (!zoneIds.empty()) {
1586 for (auto& zoneId : zoneIds) {
1587 auto itZone = zoneTiles.find(zoneId);
1588 if (itZone == zoneTiles.end()) {
1589 zoneTiles.emplace(zoneId, std::vector<FinderPosition>({ FinderPosition(tile->getX(), tile->getY(), tile->getZ()) }));
1590 } else {
1591 itZone->second.push_back(FinderPosition(tile->getX(), tile->getY(), tile->getZ()));
1592 }
1593 }
1594 } else {
1595 stream << "id: " << id << "\n";
1596 }
1597
1598 if (action > 0) {
1599 stream << "aid: " << action << "\n";
1600 }
1601 if (unique > 0) {
1602 stream << "uid: " << unique << "\n";
1603 }
1604 if (doorId > 0) {
1605 stream << "door id: " << static_cast<int>(doorId) << "\n";
1606 }
1607 if (!text.empty()) {
1608 stream << "text: " << text << "\n";
1609 }
1610 if (tp) {
1611 Position& dest = tp->getDestination();
1612 stream << "destination: " << dest.x << ", " << dest.y << ", " << dest.z << "\n";
1613 }
1614}
1615
1616void MapDrawer::WriteTooltip(Waypoint* waypoint, std::ostringstream& stream) {
1617 if (zoom > g_settings.getInteger(Config::TOOLTIP_MAX_ZOOM)) {
1618 return;
1619 }
1620
1621 if (stream.tellp() > 0) {
1622 stream << "\n";
1623 }
1624 stream << "wp: " << waypoint->name << "\n";
1625}
1626
1627void MapDrawer::DrawTile(TileLocation* location) {
1628 if (!location) {
1629 return;
1630 }
1631 Tile* tile = location->get();
1632
1633 if (!tile) {
1634 return;
1635 }
1636
1637 if (options.show_only_modified && !tile->isModified()) {
1638 return;
1639 }
1640
1641 int map_x = location->getX();
1642 int map_y = location->getY();
1643 int map_z = location->getZ();
1644
1645 // Ground-only rendering at high zoom levels
1646 bool high_zoom = zoom >= g_settings.getInteger(Config::GROUND_ONLY_ZOOM_THRESHOLD);
1647
1648 Waypoint* waypoint = canvas->editor.map.waypoints.getWaypoint(location);
1649 if (options.show_tooltips && location->getWaypointCount() > 0) {
1650 if (waypoint) {
1651 WriteTooltip(waypoint, tooltip);
1652 }
1653 }
1654
1655 bool as_minimap = options.show_as_minimap;
1656 bool only_colors = as_minimap || options.show_only_colors;
1657
1658 int offset;
1659 if (map_z <= GROUND_LAYER) {
1660 offset = (GROUND_LAYER - map_z) * TileSize;
1661 } else {
1662 offset = TileSize * (floor - map_z);
1663 }
1664
1665 int draw_x = ((map_x * TileSize) - view_scroll_x) - offset;
1666 int draw_y = ((map_y * TileSize) - view_scroll_y) - offset;
1667
1668 uint8_t r = 255, g = 255, b = 255;
1669
1670
1671 if (high_zoom) {
1672 // At high zoom only render ground
1673 if (tile->ground) {
1674 if (as_minimap) {
1675 uint8_t color = tile->getMiniMapColor();
1676 r = (uint8_t)(int(color / 36) % 6 * 51);
1677 g = (uint8_t)(int(color / 6) % 6 * 51);
1678 b = (uint8_t)(color % 6 * 51);
1679 BlitSquare(draw_x, draw_y, r, g, b, 255);
1680 } else {
1681 BlitItem(draw_x, draw_y, tile, tile->ground, false, r, g, b);
1682 }
1683 }
1684 return;
1685 }
1686
1687 // begin filters for ground tile
1688 if (!as_minimap) {
1689 bool showspecial = options.show_only_colors || options.show_special_tiles;
1690
1691 if (options.show_blocking && tile->isBlocking() && tile->size() > 0) {
1692 g = g / 3 * 2;
1693 b = b / 3 * 2;
1694 }
1695
1696 int item_count = tile->items.size();
1697 if (options.highlight_items && item_count > 0 && !tile->items.back()->isBorder()) {
1698 static const float factor[5] = { 0.75f, 0.6f, 0.48f, 0.40f, 0.33f };
1699 int idx = (item_count < 5 ? item_count : 5) - 1;
1700 g = int(g * factor[idx]);
1701 r = int(r * factor[idx]);
1702 }
1703
1704 if (options.show_spawns && location->getSpawnCount() > 0) {
1705 float f = 1.0f;
1706 for (uint32_t i = 0; i < location->getSpawnCount(); ++i) {
1707 f *= 0.7f;
1708 }
1709 g = uint8_t(g * f);
1710 b = uint8_t(b * f);
1711 }
1712
1713 if (options.show_houses && tile->isHouseTile()) {
1714 if ((int)tile->getHouseID() == current_house_id) {
1715 r /= 2;
1716 } else {
1717 r /= 2;
1718 g /= 2;
1719 }
1720 } else if (showspecial && tile->isPZ()) {
1721 r /= 2;
1722 b /= 2;
1723 }
1724
1725 if (showspecial && tile->getMapFlags() & TILESTATE_PVPZONE) {
1726 g = r / 4;
1727 b = b / 3 * 2;
1728 }
1729
1730 if (showspecial && tile->getMapFlags() & TILESTATE_NOLOGOUT) {
1731 b /= 2;
1732 }
1733
1734 if (showspecial && tile->getMapFlags() & TILESTATE_NOPVP) {
1735 g /= 2;
1736 }
1737
1738 if (options.show_zone_areas && tile->getMapFlags() & TILESTATE_ZONE_BRUSH) {
1739 size_t zones = tile->getZoneIds().size();
1740 uint16_t r16 = 0, g16 = 0, b16 = 0;
1741 for (const auto& zoneId : tile->getZoneIds()) {
1742 const uint16_t colorIndex = zoneId % colors.size();
1743 const Color colour = colors.at(colorIndex);
1744
1745 r16 += std::get<0>(colour);
1746 g16 += std::get<1>(colour);
1747 b16 += std::get<2>(colour);
1748 }
1749
1750 r = r16 / zones;
1751 g = g16 / zones;
1752 b = b16 / zones;
1753 }
1754 }
1755
1756 if (only_colors) {
1757 if (as_minimap) {
1758 uint8_t color = tile->getMiniMapColor();
1759 r = (uint8_t)(int(color / 36) % 6 * 51);
1760 g = (uint8_t)(int(color / 6) % 6 * 51);
1761 b = (uint8_t)(color % 6 * 51);
1762 BlitSquare(draw_x, draw_y, r, g, b, 255);
1763 } else if (r != 255 || g != 255 || b != 255) {
1764 BlitSquare(draw_x, draw_y, r, g, b, 128);
1765 }
1766 } else {
1767 if (tile->ground) {
1768 if (options.show_preview && zoom <= g_settings.getInteger(Config::ANIMATION_ZOOM_THRESHOLD)) {
1769 tile->ground->animate();
1770 }
1771
1772 BlitItem(draw_x, draw_y, tile, tile->ground, false, r, g, b);
1773 } else if (options.always_show_zones && (r != 255 || g != 255 || b != 255)) {
1774 DrawRawBrush(draw_x, draw_y, &g_items[SPRITE_ZONE], r, g, b, 60);
1775 }
1776 }
1777
1778 if (options.show_tooltips && map_z == floor && tile->ground) {
1779 WriteTooltip(tile, tile->ground, tooltip, tile->isHouseTile());
1780 }
1781 // end filters for ground tile
1782
1783 if (!only_colors) {
1784 if (zoom < g_settings.getInteger(Config::ITEM_DISPLAY_ZOOM_THRESHOLD) || !options.hide_items_when_zoomed) {
1785 // items on tile
1786 for (ItemVector::iterator it = tile->items.begin(); it != tile->items.end(); it++) {
1787 // item tooltip
1788 if (options.show_tooltips && map_z == floor && zoom <= g_settings.getInteger(Config::TOOLTIP_MAX_ZOOM)) {
1789 WriteTooltip(tile, *it, tooltip, tile->isHouseTile());
1790 }
1791
1792 // item animation
1793 if (options.show_preview && zoom <= g_settings.getInteger(Config::ANIMATION_ZOOM_THRESHOLD)) {
1794 (*it)->animate();
1795 }
1796
1797 // item sprite
1798 if ((*it)->isBorder()) {
1799 BlitItem(draw_x, draw_y, tile, *it, false, r, g, b);
1800 } else {
1801 r = 255, g = 255, b = 255;
1802
1803 if (options.extended_house_shader && options.show_houses && tile->isHouseTile() &&
1804 zoom <= g_settings.getInteger(Config::EFFECTS_ZOOM_THRESHOLD)) {
1805 if ((int)tile->getHouseID() == current_house_id) {
1806 r /= 2;
1807 } else {
1808 r /= 2;
1809 g /= 2;
1810 }
1811 }
1812 BlitItem(draw_x, draw_y, tile, *it, false, r, g, b);
1813 }
1814 }
1815 // monster/npc on tile
1816 if (tile->creature && options.show_creatures) {
1817 BlitCreature(draw_x, draw_y, tile->creature);
1818 }
1819 }
1820
1821 if (zoom < g_settings.getInteger(Config::SPECIAL_FEATURES_ZOOM_THRESHOLD)) {
1822 // waypoint (blue flame)
1823 if (!options.ingame && waypoint && options.show_waypoints) {
1824 BlitSpriteType(draw_x, draw_y, SPRITE_WAYPOINT, 64, 64, 255);
1825 }
1826
1827 // house exit (blue splash)
1828 if (tile->isHouseExit() && options.show_houses) {
1829 if (tile->hasHouseExit(current_house_id)) {
1830 BlitSpriteType(draw_x, draw_y, SPRITE_HOUSE_EXIT, 64, 255, 255);
1831 } else {
1832 BlitSpriteType(draw_x, draw_y, SPRITE_HOUSE_EXIT, 64, 64, 255);
1833 }
1834 }
1835
1836 // town temple (gray flag)
1837 if (options.show_towns && tile->isTownExit(editor.map) &&
1838 zoom <= g_settings.getInteger(Config::TOWN_ZONE_ZOOM_THRESHOLD)) {
1839 BlitSpriteType(draw_x, draw_y, SPRITE_TOWN_TEMPLE, 255, 255, 64, 170);
1840 }
1841
1842 // spawn (purple flame)
1843 if (tile->spawn && options.show_spawns) {
1844 if (tile->spawn->isSelected()) {
1845 BlitSpriteType(draw_x, draw_y, SPRITE_SPAWN, 128, 128, 128);
1846 } else {
1847 BlitSpriteType(draw_x, draw_y, SPRITE_SPAWN, 255, 255, 255);
1848 }
1849 }
1850
1851 // tooltips
1852 if (options.show_tooltips && zoom <= g_settings.getInteger(Config::TOOLTIP_MAX_ZOOM)) {
1853 if (location->getWaypointCount() > 0) {
1854 MakeTooltip(draw_x, draw_y, tooltip.str(), 0, 255, 0);
1855 } else {
1856 MakeTooltip(draw_x, draw_y, tooltip.str());
1857 }
1858 }
1859 tooltip.str("");
1860 }
1861 }
1862}
1863
1864void MapDrawer::DrawBrushIndicator(int x, int y, Brush* brush, uint8_t r, uint8_t g, uint8_t b) {
1865 x += (TileSize / 2);
1866 y += (TileSize / 2);
1867
1868 // 7----0----1
1869 // | |
1870 // 6--5 3--2
1871 // \/
1872 // 4
1873 static int vertexes[9][2] = {
1874 { -15, -20 }, // 0
1875 { 15, -20 }, // 1
1876 { 15, -5 }, // 2
1877 { 5, -5 }, // 3
1878 { 0, 0 }, // 4
1879 { -5, -5 }, // 5
1880 { -15, -5 }, // 6
1881 { -15, -20 }, // 7
1882 { -15, -20 }, // 0
1883 };
1884
1885 // circle
1886 glBegin(GL_TRIANGLE_FAN);
1887 glColor4ub(0x00, 0x00, 0x00, 0x50);
1888 glVertex2i(x, y);
1889 for (int i = 0; i <= 30; i++) {
1890 float angle = i * 2.0f * PI / 30;
1891 glVertex2f(cos(angle) * (TileSize / 2) + x, sin(angle) * (TileSize / 2) + y);
1892 }
1893 glEnd();
1894
1895 // background
1896 glColor4ub(r, g, b, 0xB4);
1897 glBegin(GL_POLYGON);
1898 for (int i = 0; i < 8; ++i) {
1899 glVertex2i(vertexes[i][0] + x, vertexes[i][1] + y);
1900 }
1901 glEnd();
1902
1903 // borders
1904 glColor4ub(0x00, 0x00, 0x00, 0xB4);
1905 glLineWidth(1.0);
1906 glBegin(GL_LINES);
1907 for (int i = 0; i < 8; ++i) {
1908 glVertex2i(vertexes[i][0] + x, vertexes[i][1] + y);
1909 glVertex2i(vertexes[i + 1][0] + x, vertexes[i + 1][1] + y);
1910 }
1911 glEnd();
1912}
1913
1914void MapDrawer::DrawHookIndicator(int x, int y, const ItemType& type) {
1915 glDisable(GL_TEXTURE_2D);
1916 glColor4ub(uint8_t(0), uint8_t(0), uint8_t(255), uint8_t(200));
1917 glBegin(GL_QUADS);
1918 if (type.hookSouth) {
1919 x -= 10;
1920 y += 10;
1921 glVertex2f(x, y);
1922 glVertex2f(x + 10, y);
1923 glVertex2f(x + 20, y + 10);
1924 glVertex2f(x + 10, y + 10);
1925 } else if (type.hookEast) {
1926 x += 10;
1927 y -= 10;
1928 glVertex2f(x, y);
1929 glVertex2f(x + 10, y + 10);
1930 glVertex2f(x + 10, y + 20);
1931 glVertex2f(x, y + 10);
1932 }
1933 glEnd();
1934 glEnable(GL_TEXTURE_2D);
1935}
1936
1937void MapDrawer::DrawTooltips() {
1938 for (std::vector<MapTooltip*>::const_iterator it = tooltips.begin(); it != tooltips.end(); ++it) {
1939 MapTooltip* tooltip = (*it);
1940 const char* text = tooltip->text.c_str();
1941 float line_width = 0.0f;
1942 float width = 2.0f;
1943 float height = 14.0f;
1944 int char_count = 0;
1945 int line_char_count = 0;
1946
1947 for (const char* c = text; *c != '\0'; c++) {
1948 if (*c == '\n' || (line_char_count >= MapTooltip::MAX_CHARS_PER_LINE && *c == ' ')) {
1949 height += 14.0f;
1950 line_width = 0.0f;
1951 line_char_count = 0;
1952 } else {
1953 line_width += glutBitmapWidth(GLUT_BITMAP_HELVETICA_12, *c);
1954 }
1955 width = std::max<float>(width, line_width);
1956 char_count++;
1957 line_char_count++;
1958
1959 if (tooltip->ellipsis && char_count > (MapTooltip::MAX_CHARS + 3)) {
1960 break;
1961 }
1962 }
1963
1964 float scale = zoom < 1.0f ? zoom : 1.0f;
1965
1966 width = (width + 8.0f) * scale;
1967 height = (height + 4.0f) * scale;
1968
1969 float x = tooltip->x + (TileSize / 2.0f);
1970 float y = tooltip->y;
1971 float center = width / 2.0f;
1972 float space = (7.0f * scale);
1973 float startx = x - center;
1974 float endx = x + center;
1975 float starty = y - (height + space);
1976 float endy = y - space;
1977
1978 // 7----0----1
1979 // | |
1980 // 6--5 3--2
1981 // \/
1982 // 4
1983 float vertexes[9][2] = {
1984 { x, starty }, // 0
1985 { endx, starty }, // 1
1986 { endx, endy }, // 2
1987 { x + space, endy }, // 3
1988 { x, y }, // 4
1989 { x - space, endy }, // 5
1990 { startx, endy }, // 6
1991 { startx, starty }, // 7
1992 { x, starty }, // 0
1993 };
1994
1995 // background
1996 glColor4ub(tooltip->r, tooltip->g, tooltip->b, 255);
1997 glBegin(GL_POLYGON);
1998 for (int i = 0; i < 8; ++i) {
1999 glVertex2f(vertexes[i][0], vertexes[i][1]);
2000 }
2001 glEnd();
2002
2003 // borders
2004 glColor4ub(0, 0, 0, 255);
2005 glLineWidth(1.0);
2006 glBegin(GL_LINES);
2007 for (int i = 0; i < 8; ++i) {
2008 glVertex2f(vertexes[i][0], vertexes[i][1]);
2009 glVertex2f(vertexes[i + 1][0], vertexes[i + 1][1]);
2010 }
2011 glEnd();
2012
2013 // text
2014 if (zoom <= 1.0) {
2015 startx += (3.0f * scale);
2016 starty += (14.0f * scale);
2017 glColor4ub(0, 0, 0, 255);
2018 glRasterPos2f(startx, starty);
2019 char_count = 0;
2020 line_char_count = 0;
2021 for (const char* c = text; *c != '\0'; c++) {
2022 if (*c == '\n' || (line_char_count >= MapTooltip::MAX_CHARS_PER_LINE && *c == ' ')) {
2023 starty += (14.0f * scale);
2024 glRasterPos2f(startx, starty);
2025 line_char_count = 0;
2026 }
2027 char_count++;
2028 line_char_count++;
2029
2030 if (tooltip->ellipsis && char_count >= MapTooltip::MAX_CHARS) {
2031 glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, '.');
2032 if (char_count >= (MapTooltip::MAX_CHARS + 2)) {
2033 break;
2034 }
2035 } else if (!iscntrl(*c)) {
2036 glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, *c);
2037 }
2038 }
2039 }
2040 }
2041}
2042
2043void MapDrawer::DrawLight() {
2044 // draw in-game light
2045 light_drawer->draw(start_x, start_y, end_x, end_y, view_scroll_x, view_scroll_y, options.experimental_fog);
2046}
2047
2048void MapDrawer::MakeTooltip(int screenx, int screeny, const std::string& text, uint8_t r, uint8_t g, uint8_t b) {
2049 if (zoom > g_settings.getInteger(Config::TOOLTIP_MAX_ZOOM) || text.empty()) {
2050 return;
2051 }
2052
2053 MapTooltip* tooltip = newd MapTooltip(screenx, screeny, text, r, g, b);
2054 tooltip->checkLineEnding();
2055 tooltips.push_back(tooltip);
2056}
2057
2058void MapDrawer::AddLight(TileLocation* location) {
2059 if (!options.isDrawLight() || !location || zoom > g_settings.getInteger(Config::LIGHT_ZOOM_THRESHOLD)) {
2060 return;
2061 }
2062
2063 auto tile = location->get();
2064 if (!tile) {
2065 return;
2066 }
2067
2068 auto& position = location->getPosition();
2069
2070 if (tile->ground) {
2071 if (tile->ground->hasLight()) {
2072 light_drawer->addLight(position.x, position.y, position.z, tile->ground->getLight());
2073 }
2074 }
2075
2076 bool hidden = options.hide_items_when_zoomed && zoom > g_settings.getInteger(Config::ITEM_DISPLAY_ZOOM_THRESHOLD);
2077 if (!hidden && !tile->items.empty()) {
2078 for (auto item : tile->items) {
2079 if (item->hasLight()) {
2080 light_drawer->addLight(position.x, position.y, position.z, item->getLight());
2081 }
2082 }
2083 }
2084}
2085
2086void MapDrawer::getColor(Brush* brush, const Position& position, uint8_t& r, uint8_t& g, uint8_t& b) {
2087 if (brush->canDraw(&editor.map, position)) {
2088 if (brush->isWaypoint()) {
2089 r = 0x00;
2090 g = 0xff, b = 0x00;
2091 } else {
2092 r = 0x00;
2093 g = 0x00, b = 0xff;
2094 }
2095 } else {
2096 r = 0xff;
2097 g = 0x00, b = 0x00;
2098 }
2099}
2100
2101void MapDrawer::TakeScreenshot(uint8_t* screenshot_buffer) {
2102 glFinish(); // Wait for the operation to finish
2103
2104 glPixelStorei(GL_PACK_ALIGNMENT, 1); // 1 byte alignment
2105
2106 for (int i = 0; i < screensize_y; ++i) {
2107 glReadPixels(0, screensize_y - i, screensize_x, 1, GL_RGB, GL_UNSIGNED_BYTE, (GLubyte*)(screenshot_buffer) + 3 * screensize_x * i);
2108 }
2109}
2110
2111void MapDrawer::glBlitTexture(int sx, int sy, int texture_number, int red, int green, int blue, int alpha) {
2112 if (texture_number != 0) {
2113 glBindTexture(GL_TEXTURE_2D, texture_number);
2114 glColor4ub(uint8_t(red), uint8_t(green), uint8_t(blue), uint8_t(alpha));
2115 glBegin(GL_QUADS);
2116 glTexCoord2f(0.f, 0.f);
2117 glVertex2f(sx, sy);
2118 glTexCoord2f(1.f, 0.f);
2119 glVertex2f(sx + TileSize, sy);
2120 glTexCoord2f(1.f, 1.f);
2121 glVertex2f(sx + TileSize, sy + TileSize);
2122 glTexCoord2f(0.f, 1.f);
2123 glVertex2f(sx, sy + TileSize);
2124 glEnd();
2125 }
2126}
2127
2128void MapDrawer::glBlitSquare(int sx, int sy, int red, int green, int blue, int alpha, int size) {
2129 if (size == 0) {
2130 size = TileSize;
2131 }
2132
2133 glColor4ub(uint8_t(red), uint8_t(green), uint8_t(blue), uint8_t(alpha));
2134 glBegin(GL_QUADS);
2135 glVertex2f(sx, sy);
2136 glVertex2f(sx + size, sy);
2137 glVertex2f(sx + size, sy + size);
2138 glVertex2f(sx, sy + size);
2139 glEnd();
2140}
2141
2142void MapDrawer::glColor(wxColor color) {
2143 glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha());
2144}
2145
2146void MapDrawer::glColor(MapDrawer::BrushColor color) {
2147 switch (color) {
2148 case COLOR_BRUSH:
2149 glColor4ub(
2150 g_settings.getInteger(Config::CURSOR_RED),
2151 g_settings.getInteger(Config::CURSOR_GREEN),
2152 g_settings.getInteger(Config::CURSOR_BLUE),
2153 g_settings.getInteger(Config::CURSOR_ALPHA)
2154 );
2155 break;
2156
2157 case COLOR_FLAG_BRUSH:
2158 case COLOR_HOUSE_BRUSH:
2159 glColor4ub(
2160 g_settings.getInteger(Config::CURSOR_ALT_RED),
2161 g_settings.getInteger(Config::CURSOR_ALT_GREEN),
2162 g_settings.getInteger(Config::CURSOR_ALT_BLUE),
2163 g_settings.getInteger(Config::CURSOR_ALT_ALPHA)
2164 );
2165 break;
2166
2167 case COLOR_SPAWN_BRUSH:
2168 glColor4ub(166, 0, 0, 128);
2169 break;
2170
2171 case COLOR_ERASER:
2172 glColor4ub(166, 0, 0, 128);
2173 break;
2174
2175 case COLOR_VALID:
2176 glColor4ub(0, 166, 0, 128);
2177 break;
2178
2179 case COLOR_INVALID:
2180 glColor4ub(166, 0, 0, 128);
2181 break;
2182
2183 default:
2184 glColor4ub(255, 255, 255, 128);
2185 break;
2186 }
2187}
2188
2189void MapDrawer::glColorCheck(Brush* brush, const Position& pos) {
2190 if (brush->canDraw(&editor.map, pos)) {
2191 glColor(COLOR_VALID);
2192 } else {
2193 glColor(COLOR_INVALID);
2194 }
2195}
2196
2197void MapDrawer::drawRect(int x, int y, int w, int h, const wxColor& color, int width) {
2198 glLineWidth(width);
2199 glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha());
2200 glBegin(GL_LINE_STRIP);
2201 glVertex2f(x, y);
2202 glVertex2f(x + w, y);
2203 glVertex2f(x + w, y + h);
2204 glVertex2f(x, y + h);
2205 glVertex2f(x, y);
2206 glEnd();
2207}
2208
2209void MapDrawer::drawFilledRect(int x, int y, int w, int h, const wxColor& color) {
2210 glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha());
2211 glBegin(GL_QUADS);
2212 glVertex2f(x, y);
2213 glVertex2f(x + w, y);
2214 glVertex2f(x + w, y + h);
2215 glVertex2f(x, y + h);
2216 glEnd();
2217}
2218