1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program 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// This program 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#include "object/tilemap.hpp"
18
19#include <tuple>
20
21#include "editor/editor.hpp"
22#include "supertux/debug.hpp"
23#include "supertux/globals.hpp"
24#include "supertux/sector.hpp"
25#include "supertux/tile.hpp"
26#include "supertux/tile_set.hpp"
27#include "util/reader.hpp"
28#include "util/reader_mapping.hpp"
29#include "util/writer.hpp"
30#include "video/drawing_context.hpp"
31#include "video/layer.hpp"
32#include "video/surface.hpp"
33
34TileMap::TileMap(const TileSet *new_tileset) :
35 ExposedObject<TileMap, scripting::TileMap>(this),
36 PathObject(),
37 m_editor_active(true),
38 m_tileset(new_tileset),
39 m_tiles(),
40 m_real_solid(false),
41 m_effective_solid(false),
42 m_speed_x(1),
43 m_speed_y(1),
44 m_width(0),
45 m_height(0),
46 m_z_pos(0),
47 m_offset(Vector(0,0)),
48 m_movement(0,0),
49 m_flip(NO_FLIP),
50 m_alpha(1.0),
51 m_current_alpha(1.0),
52 m_remaining_fade_time(0),
53 m_tint(1, 1, 1),
54 m_current_tint(1, 1, 1),
55 m_remaining_tint_fade_time(0),
56 m_running(false),
57 m_draw_target(DrawingTarget::COLORMAP),
58 m_new_size_x(0),
59 m_new_size_y(0),
60 m_new_offset_x(0),
61 m_new_offset_y(0),
62 m_add_path(false)
63{
64}
65
66TileMap::TileMap(const TileSet *tileset_, const ReaderMapping& reader) :
67 GameObject(reader),
68 ExposedObject<TileMap, scripting::TileMap>(this),
69 PathObject(),
70 m_editor_active(true),
71 m_tileset(tileset_),
72 m_tiles(),
73 m_real_solid(false),
74 m_effective_solid(false),
75 m_speed_x(1),
76 m_speed_y(1),
77 m_width(-1),
78 m_height(-1),
79 m_z_pos(0),
80 m_offset(Vector(0,0)),
81 m_movement(Vector(0,0)),
82 m_flip(NO_FLIP),
83 m_alpha(1.0),
84 m_current_alpha(1.0),
85 m_remaining_fade_time(0),
86 m_tint(1, 1, 1),
87 m_current_tint(1, 1, 1),
88 m_remaining_tint_fade_time(0),
89 m_running(false),
90 m_draw_target(DrawingTarget::COLORMAP),
91 m_new_size_x(0),
92 m_new_size_y(0),
93 m_new_offset_x(0),
94 m_new_offset_y(0),
95 m_add_path(false)
96{
97 assert(m_tileset);
98
99 reader.get("solid", m_real_solid);
100
101 bool backward_compatibility_fudge = false;
102
103 if (!reader.get("speed-x", m_speed_x)) {
104 if (reader.get("speed", m_speed_x)) {
105 backward_compatibility_fudge = true;
106 }
107 }
108
109 if (!reader.get("speed-y", m_speed_y)) {
110 if (backward_compatibility_fudge) {
111 m_speed_y = m_speed_x;
112 }
113 }
114
115 m_z_pos = reader_get_layer(reader, 0);
116
117 if (!Editor::is_active()) {
118 if (m_real_solid && ((m_speed_x != 1) || (m_speed_y != 1))) {
119 log_warning << "Speed of solid tilemap is not 1. fixing" << std::endl;
120 m_speed_x = 1;
121 m_speed_y = 1;
122 }
123 }
124
125 init_path(reader, false);
126
127 std::string draw_target_s = "normal";
128 reader.get("draw-target", draw_target_s);
129 if (draw_target_s == "normal") m_draw_target = DrawingTarget::COLORMAP;
130 if (draw_target_s == "lightmap") m_draw_target = DrawingTarget::LIGHTMAP;
131
132 if (reader.get("alpha", m_alpha)) {
133 m_current_alpha = m_alpha;
134 }
135
136 std::vector<float> vColor;
137 if (reader.get("tint", vColor)) {
138 m_current_tint = Color(vColor);
139 m_tint = m_current_tint;
140 }
141
142 /* Initialize effective_solid based on real_solid and current_alpha. */
143 m_effective_solid = m_real_solid;
144 update_effective_solid ();
145
146 reader.get("width", m_width);
147 reader.get("height", m_height);
148 if (m_width < 0 || m_height < 0) {
149 //throw std::runtime_error("Invalid/No width/height specified in tilemap.");
150 m_width = 0;
151 m_height = 0;
152 m_tiles.clear();
153 resize(static_cast<int>(Sector::get().get_width() / 32.0f),
154 static_cast<int>(Sector::get().get_height() / 32.0f));
155 m_editor_active = false;
156 } else {
157 if (!reader.get("tiles", m_tiles))
158 throw std::runtime_error("No tiles in tilemap.");
159
160 if (int(m_tiles.size()) != m_width * m_height) {
161 throw std::runtime_error("wrong number of tiles in tilemap.");
162 }
163 }
164
165 bool empty = true;
166
167 // make sure all tiles used on the tilemap are loaded and tilemap isn't empty
168 for (const auto& tile : m_tiles) {
169 if (tile != 0) {
170 empty = false;
171 }
172
173 m_tileset->get(tile);
174 }
175
176 if (empty)
177 {
178 log_info << "Tilemap '" << get_name() << "', z-pos '" << m_z_pos << "' is empty." << std::endl;
179 }
180}
181
182void
183TileMap::finish_construction()
184{
185 if (get_path()) {
186 Vector v = get_path()->get_base();
187 set_offset(v);
188 }
189
190 m_add_path = get_walker() && get_path() && get_path()->is_valid();
191}
192
193TileMap::~TileMap()
194{
195}
196
197void
198TileMap::float_channel(float target, float &current, float remaining_time, float dt_sec)
199{
200 float amt = (target - current) / (remaining_time / dt_sec);
201 if (amt > 0) current = std::min(current + amt, target);
202 if (amt < 0) current = std::max(current + amt, target);
203}
204
205ObjectSettings
206TileMap::get_settings()
207{
208 m_new_size_x = m_width;
209 m_new_size_y = m_height;
210 m_new_offset_x = 0;
211 m_new_offset_y = 0;
212
213 ObjectSettings result = GameObject::get_settings();
214
215 result.add_bool(_("Solid"), &m_real_solid, "solid");
216 result.add_int(_("Resize offset x"), &m_new_offset_x);
217 result.add_int(_("Resize offset y"), &m_new_offset_y);
218
219 result.add_int(_("Width"), &m_new_size_x);
220 result.add_int(_("Height"), &m_new_size_y);
221
222 result.add_float(_("Alpha"), &m_alpha, "alpha", 1.0f);
223 result.add_float(_("Speed x"), &m_speed_x, "speed-x", 1.0f);
224 result.add_float(_("Speed y"), &m_speed_y, "speed-y", 1.0f);
225 result.add_color(_("Tint"), &m_tint, "tint", Color::WHITE);
226 result.add_int(_("Z-pos"), &m_z_pos, "z-pos");
227 result.add_enum(_("Draw target"), reinterpret_cast<int*>(&m_draw_target),
228 {_("Normal"), _("Lightmap")},
229 {"normal", "lightmap"},
230 static_cast<int>(DrawingTarget::COLORMAP),
231 "draw-target");
232
233 result.add_path_ref(_("Path"), get_path_ref(), "path-ref");
234 m_add_path = get_walker() && get_path() && get_path()->is_valid();
235 result.add_bool(_("Following path"), &m_add_path);
236
237 if (get_walker() && get_path() && get_path()->is_valid()) {
238 m_running = get_walker()->is_running();
239 result.add_walk_mode(_("Path Mode"), &get_path()->m_mode, {}, {});
240 result.add_bool(_("Running"), &m_running, "running", false);
241 }
242
243 result.add_tiles(_("Tiles"), this, "tiles");
244
245 result.reorder({"solid", "running", "speed-x", "speed-y", "tint", "draw-target", "alpha", "z-pos", "name", "path-ref", "width", "height", "tiles"});
246
247 if (!m_editor_active) {
248 result.add_remove();
249 }
250
251 return result;
252}
253
254void
255TileMap::after_editor_set()
256{
257 if ((m_new_size_x != m_width || m_new_size_y != m_height ||
258 m_new_offset_x || m_new_offset_y) &&
259 m_new_size_x > 0 && m_new_size_y > 0) {
260 resize(m_new_size_x, m_new_size_y, 0, m_new_offset_x, m_new_offset_y);
261 }
262
263 if (get_walker() && get_path() && get_path()->is_valid()) {
264 if (!m_add_path) {
265 get_path()->m_nodes.clear();
266 }
267 } else {
268 if (m_add_path) {
269 init_path_pos(m_offset, m_running);
270 }
271 }
272
273 m_current_tint = m_tint;
274 m_current_alpha = m_alpha;
275}
276
277void
278TileMap::update(float dt_sec)
279{
280 // handle tilemap fading
281 if (m_current_alpha != m_alpha) {
282 m_remaining_fade_time = std::max(0.0f, m_remaining_fade_time - dt_sec);
283 if (m_remaining_fade_time == 0.0f) {
284 m_current_alpha = m_alpha;
285 } else {
286 float_channel(m_alpha, m_current_alpha, m_remaining_fade_time, dt_sec);
287 }
288 update_effective_solid ();
289 }
290
291 // handle tint fading
292 if (m_current_tint.red != m_tint.red || m_current_tint.green != m_tint.green ||
293 m_current_tint.blue != m_tint.blue || m_current_tint.alpha != m_tint.alpha) {
294
295 m_remaining_tint_fade_time = std::max(0.0f, m_remaining_tint_fade_time - dt_sec);
296 if (m_remaining_tint_fade_time == 0.0f) {
297 m_current_tint = m_tint;
298 } else {
299 float_channel(m_tint.red , m_current_tint.red , m_remaining_tint_fade_time, dt_sec);
300 float_channel(m_tint.green, m_current_tint.green, m_remaining_tint_fade_time, dt_sec);
301 float_channel(m_tint.blue , m_current_tint.blue , m_remaining_tint_fade_time, dt_sec);
302 float_channel(m_tint.alpha, m_current_tint.alpha, m_remaining_tint_fade_time, dt_sec);
303 }
304 }
305
306 m_movement = Vector(0,0);
307 // if we have a path to follow, follow it
308 if (get_walker()) {
309 get_walker()->update(dt_sec);
310 Vector v = get_walker()->get_pos();
311 if (get_path() && get_path()->is_valid()) {
312 m_movement = v - get_offset();
313 set_offset(v);
314 } else {
315 set_offset(Vector(0, 0));
316 }
317 }
318}
319
320void
321TileMap::editor_update()
322{
323 if (get_walker()) {
324 if (get_path() && get_path()->is_valid()) {
325 m_movement = get_walker()->get_pos() - get_offset();
326 set_offset(get_walker()->get_pos());
327 } else {
328 set_offset(Vector(0, 0));
329 }
330 }
331}
332
333void
334TileMap::draw(DrawingContext& context)
335{
336 // skip draw if current opacity is 0.0
337 if (m_current_alpha == 0.0f) return;
338
339 context.push_transform();
340
341 if (m_flip != NO_FLIP) context.set_flip(m_flip);
342
343 if (m_editor_active) {
344 if (m_current_alpha != 1.0f) {
345 context.set_alpha(m_current_alpha);
346 }
347 } else {
348 context.set_alpha(m_current_alpha/2);
349 }
350
351 const float trans_x = context.get_translation().x;
352 const float trans_y = context.get_translation().y;
353 const bool normal_speed = m_editor_active && Editor::is_active();
354 context.set_translation(Vector(trans_x * (normal_speed ? 1.0f : m_speed_x),
355 trans_y * (normal_speed ? 1.0f : m_speed_y)));
356
357 Rectf draw_rect = context.get_cliprect();
358 Rect t_draw_rect = get_tiles_overlapping(draw_rect);
359 Vector start = get_tile_position(t_draw_rect.left, t_draw_rect.top);
360
361 Vector pos;
362 int tx, ty;
363
364 std::unordered_map<SurfacePtr,
365 std::tuple<std::vector<Rectf>,
366 std::vector<Rectf>>> batches;
367
368 for (pos.x = start.x, tx = t_draw_rect.left; tx < t_draw_rect.right; pos.x += 32, ++tx) {
369 for (pos.y = start.y, ty = t_draw_rect.top; ty < t_draw_rect.bottom; pos.y += 32, ++ty) {
370 int index = ty*m_width + tx;
371 assert (index >= 0);
372 assert (index < (m_width * m_height));
373
374 if (m_tiles[index] == 0) continue;
375 const Tile& tile = m_tileset->get(m_tiles[index]);
376
377 if (g_debug.show_collision_rects) {
378 tile.draw_debug(context.color(), pos, LAYER_FOREGROUND1);
379 }
380
381 const SurfacePtr& surface = Editor::is_active() ? tile.get_current_editor_surface() : tile.get_current_surface();
382 if (surface) {
383 std::get<0>(batches[surface]).emplace_back(surface->get_region());
384 std::get<1>(batches[surface]).emplace_back(pos,
385 Sizef(static_cast<float>(surface->get_width()),
386 static_cast<float>(surface->get_height())));
387 }
388 }
389 }
390
391 Canvas& canvas = context.get_canvas(m_draw_target);
392
393 for (auto& it : batches)
394 {
395 const SurfacePtr& surface = it.first;
396 if (surface) {
397 canvas.draw_surface_batch(surface,
398 std::move(std::get<0>(it.second)),
399 std::move(std::get<1>(it.second)),
400 m_current_tint, m_z_pos);
401 }
402 }
403
404 context.pop_transform();
405}
406
407void
408TileMap::goto_node(int node_no)
409{
410 if (!get_walker()) return;
411 get_walker()->goto_node(node_no);
412}
413
414void
415TileMap::start_moving()
416{
417 if (!get_walker()) return;
418 get_walker()->start_moving();
419}
420
421void
422TileMap::stop_moving()
423{
424 if (!get_walker()) return;
425 get_walker()->stop_moving();
426}
427
428void
429TileMap::set(int newwidth, int newheight, const std::vector<unsigned int>&newt,
430 int new_z_pos, bool newsolid)
431{
432 if (int(newt.size()) != newwidth * newheight)
433 throw std::runtime_error("Wrong tilecount count.");
434
435 m_width = newwidth;
436 m_height = newheight;
437
438 m_tiles.resize(newt.size());
439 m_tiles = newt;
440
441 if (new_z_pos > (LAYER_GUI - 100))
442 m_z_pos = LAYER_GUI - 100;
443 else
444 m_z_pos = new_z_pos;
445 m_real_solid = newsolid;
446 update_effective_solid ();
447
448 // make sure all tiles are loaded
449 for (const auto& tile : m_tiles)
450 m_tileset->get(tile);
451}
452
453void
454TileMap::resize(int new_width, int new_height, int fill_id,
455 int xoffset, int yoffset)
456{
457 if (new_width < m_width) {
458 // remap tiles for new width
459 for (int y = 0; y < m_height && y < new_height; ++y) {
460 for (int x = 0; x < new_width; ++x) {
461 m_tiles[y * new_width + x] = m_tiles[y * m_width + x];
462 }
463 }
464 }
465
466 m_tiles.resize(new_width * new_height, fill_id);
467
468 if (new_width > m_width) {
469 // remap tiles
470 for (int y = std::min(m_height, new_height)-1; y >= 0; --y) {
471 for (int x = new_width-1; x >= 0; --x) {
472 if (x >= m_width) {
473 m_tiles[y * new_width + x] = fill_id;
474 continue;
475 }
476
477 m_tiles[y * new_width + x] = m_tiles[y * m_width + x];
478 }
479 }
480 }
481
482 m_height = new_height;
483 m_width = new_width;
484
485 //Apply offset
486 if (xoffset || yoffset) {
487 for (int y = 0; y < m_height; y++) {
488 int Y = (yoffset < 0) ? y : (m_height - y - 1);
489 for (int x = 0; x < m_width; x++) {
490 int X = (xoffset < 0) ? x : (m_width - x - 1);
491 if (Y - yoffset < 0 || Y - yoffset >= m_height ||
492 X - xoffset < 0 || X - xoffset >= m_width) {
493 m_tiles[Y * new_width + X] = fill_id;
494 } else {
495 m_tiles[Y * new_width + X] = m_tiles[(Y - yoffset) * m_width + X - xoffset];
496 }
497 }
498 }
499 }
500}
501
502void TileMap::resize(const Size& newsize, const Size& resize_offset) {
503 resize(newsize.width, newsize.height, 0, resize_offset.width, resize_offset.height);
504}
505
506Rect
507TileMap::get_tiles_overlapping(const Rectf &rect) const
508{
509 Rectf rect2 = rect;
510 rect2.move(-m_offset);
511
512 int t_left = std::max(0 , int(floorf(rect2.get_left () / 32)));
513 int t_right = std::min(m_width , int(ceilf (rect2.get_right () / 32)));
514 int t_top = std::max(0 , int(floorf(rect2.get_top () / 32)));
515 int t_bottom = std::min(m_height, int(ceilf (rect2.get_bottom() / 32)));
516 return Rect(t_left, t_top, t_right, t_bottom);
517}
518
519void
520TileMap::set_solid(bool solid)
521{
522 m_real_solid = solid;
523 update_effective_solid ();
524}
525
526uint32_t
527TileMap::get_tile_id(int x, int y) const
528{
529 if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
530 //log_warning << "tile outside tilemap requested" << std::endl;
531 return 0;
532 }
533
534 return m_tiles[y*m_width + x];
535}
536
537const Tile&
538TileMap::get_tile(int x, int y) const
539{
540 uint32_t id = get_tile_id(x, y);
541 return m_tileset->get(id);
542}
543
544uint32_t
545TileMap::get_tile_id_at(const Vector& pos) const
546{
547 Vector xy = (pos - m_offset) / 32;
548 return get_tile_id(int(xy.x), int(xy.y));
549}
550
551const Tile&
552TileMap::get_tile_at(const Vector& pos) const
553{
554 uint32_t id = get_tile_id_at(pos);
555 return m_tileset->get(id);
556}
557
558void
559TileMap::change(int x, int y, uint32_t newtile)
560{
561 assert(x >= 0 && x < m_width && y >= 0 && y < m_height);
562 m_tiles[y*m_width + x] = newtile;
563}
564
565void
566TileMap::change_at(const Vector& pos, uint32_t newtile)
567{
568 Vector xy = (pos - m_offset) / 32;
569 change(int(xy.x), int(xy.y), newtile);
570}
571
572void
573TileMap::change_all(uint32_t oldtile, uint32_t newtile)
574{
575 for (int x = 0; x < get_width(); x++) {
576 for (int y = 0; y < get_height(); y++) {
577 if (get_tile_id(x,y) != oldtile)
578 continue;
579
580 change(x,y,newtile);
581 }
582 }
583}
584
585void
586TileMap::fade(float alpha_, float seconds)
587{
588 m_alpha = alpha_;
589 m_remaining_fade_time = seconds;
590}
591
592void
593TileMap::tint_fade(const Color& new_tint, float seconds)
594{
595 m_tint = new_tint;
596 m_remaining_tint_fade_time = seconds;
597}
598
599void
600TileMap::set_alpha(float alpha_)
601{
602 m_alpha = alpha_;
603 m_current_alpha = m_alpha;
604 m_remaining_fade_time = 0;
605 update_effective_solid ();
606}
607
608float
609TileMap::get_alpha() const
610{
611 return m_current_alpha;
612}
613
614void
615TileMap::move_by(const Vector& shift)
616{
617 if (!get_path()) {
618 init_path_pos(m_offset);
619 m_add_path = true;
620 }
621 get_path()->move_by(shift);
622 m_offset += shift;
623}
624
625void
626TileMap::update_effective_solid()
627{
628 if (!m_real_solid)
629 m_effective_solid = false;
630 else if (m_effective_solid && (m_current_alpha < 0.25f))
631 m_effective_solid = false;
632 else if (!m_effective_solid && (m_current_alpha >= 0.75f))
633 m_effective_solid = true;
634}
635
636void
637TileMap::set_tileset(const TileSet* new_tileset)
638{
639 m_tileset = new_tileset;
640}
641
642/* EOF */
643