diff --git a/SCsub b/SCsub index eea2296..5a266a6 100644 --- a/SCsub +++ b/SCsub @@ -1,7 +1,10 @@ Import('env') env.add_source_files(env.modules_sources,"register_types.cpp") +env.add_source_files(env.modules_sources,"tile_map.cpp") +env.add_source_files(env.modules_sources,"tile_set.cpp") -#if env["tools"]: -# env.add_source_files(env.modules_sources, "tile_editor/*.cpp") +if env["tools"]: + env.add_source_files(env.modules_sources, "tile_map_editor_plugin.cpp") + env.add_source_files(env.modules_sources, "tile_set_editor_plugin.cpp") diff --git a/register_types.cpp b/register_types.cpp index 264d6fc..ab98ef8 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -22,13 +22,22 @@ SOFTWARE. #include "register_types.h" +#include "tile_map.h" +#include "tile_set.h" + #ifdef TOOLS_ENABLED +#include "tile_map_editor_plugin.h" +#include "tile_set_editor_plugin.h" #endif void register_rtile_map_types() { #ifdef TOOLS_ENABLED + EditorPlugins::add_by_type(); + EditorPlugins::add_by_type(); #endif + ClassDB::register_class(); + ClassDB::register_class(); } void unregister_rtile_map_types() { diff --git a/tile_map.cpp b/tile_map.cpp new file mode 100644 index 0000000..baecf93 --- /dev/null +++ b/tile_map.cpp @@ -0,0 +1,1934 @@ +/*************************************************************************/ +/* tile_map.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_map.h" + +#include "scene/2d/collision_object_2d.h" +#include "core/io/marshalls.h" +#include "core/method_bind_ext.gen.inc" +#include "core/os/os.h" +#include "scene/2d/area_2d.h" +#include "servers/physics_2d_server.h" + +int RTileMap::_get_quadrant_size() const { + if (y_sort_mode) { + return 1; + } else { + return quadrant_size; + } +} + +void RTileMap::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + Node2D *c = this; + while (c) { + navigation = Object::cast_to(c); + if (navigation) { + break; + } + + c = Object::cast_to(c->get_parent()); + } + + if (use_parent) { + _clear_quadrants(); + collision_parent = Object::cast_to(get_parent()); + } + + pending_update = true; + _recreate_quadrants(); + update_dirty_quadrants(); + RID space = get_world_2d()->get_space(); + _update_quadrant_transform(); + _update_quadrant_space(space); + update_configuration_warning(); + + } break; + + case NOTIFICATION_EXIT_TREE: { + _update_quadrant_space(RID()); + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + if (navigation) { + for (Map::Element *F = q.navpoly_ids.front(); F; F = F->next()) { + navigation->navpoly_remove(F->get().id); + } + q.navpoly_ids.clear(); + } + + if (collision_parent) { + collision_parent->remove_shape_owner(q.shape_owner_id); + q.shape_owner_id = -1; + } + + for (Map::Element *F = q.occluder_instances.front(); F; F = F->next()) { + if (F->get().id.is_valid()) { + VS::get_singleton()->free(F->get().id); + } + } + q.occluder_instances.clear(); + } + + collision_parent = nullptr; + navigation = nullptr; + + } break; + + case NOTIFICATION_TRANSFORM_CHANGED: { + //move stuff + _update_quadrant_transform(); + + } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + if (use_parent) { + _recreate_quadrants(); + } + + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + for (Map::Element *F = E->get().occluder_instances.front(); F; F = F->next()) { + VS::get_singleton()->canvas_light_occluder_set_enabled(F->get().id, is_visible()); + } + } + + } break; + } +} + +void RTileMap::_update_quadrant_space(const RID &p_space) { + if (!use_parent) { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_space(q.body, p_space); + } + } +} + +void RTileMap::_update_quadrant_transform() { + if (!is_inside_tree()) { + return; + } + + Transform2D global_transform = get_global_transform(); + + Transform2D local_transform; + if (collision_parent) { + local_transform = get_transform(); + } + + Transform2D nav_rel; + if (navigation) { + nav_rel = get_relative_transform_to_parent(navigation); + } + + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Transform2D xform; + xform.set_origin(q.pos); + + if (!use_parent) { + xform = global_transform * xform; + Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + } + + if (navigation) { + for (Map::Element *F = q.navpoly_ids.front(); F; F = F->next()) { + navigation->navpoly_set_transform(F->get().id, nav_rel * F->get().xform); + } + } + + for (Map::Element *F = q.occluder_instances.front(); F; F = F->next()) { + VS::get_singleton()->canvas_light_occluder_set_transform(F->get().id, global_transform * F->get().xform); + } + } +} + +void RTileMap::set_tileset(const Ref &p_tileset) { + if (tile_set.is_valid()) { + tile_set->disconnect("changed", this, "_recreate_quadrants"); + tile_set->remove_change_receptor(this); + } + + _clear_quadrants(); + tile_set = p_tileset; + + if (tile_set.is_valid()) { + tile_set->connect("changed", this, "_recreate_quadrants"); + tile_set->add_change_receptor(this); + } else { + clear(); + } + + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +Ref RTileMap::get_tileset() const { + return tile_set; +} + +void RTileMap::set_cell_size(Size2 p_size) { + ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); + + _clear_quadrants(); + cell_size = p_size; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +Size2 RTileMap::get_cell_size() const { + return cell_size; +} + +void RTileMap::set_quadrant_size(int p_size) { + ERR_FAIL_COND_MSG(p_size < 1, "Quadrant size cannot be smaller than 1."); + + _clear_quadrants(); + quadrant_size = p_size; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +int RTileMap::get_quadrant_size() const { + return quadrant_size; +} + +void RTileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc) { + Size2 s = p_sc; + Vector2 offset = p_offset; + + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.y += cell_size.y; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset += cell_size / 2; + } + + if (s.y > s.x) { + if ((p_cell.flip_h && (p_cell.flip_v || p_cell.transpose)) || (p_cell.flip_v && !p_cell.transpose)) { + offset.y += s.y - s.x; + } + } else if (s.y < s.x) { + if ((p_cell.flip_v && (p_cell.flip_h || p_cell.transpose)) || (p_cell.flip_h && !p_cell.transpose)) { + offset.x += s.x - s.y; + } + } + } + + if (p_cell.transpose) { + SWAP(xform.elements[0].x, xform.elements[0].y); + SWAP(xform.elements[1].x, xform.elements[1].y); + SWAP(offset.x, offset.y); + SWAP(s.x, s.y); + } + + if (p_cell.flip_h) { + xform.elements[0].x = -xform.elements[0].x; + xform.elements[1].x = -xform.elements[1].x; + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT || tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.x = s.x - offset.x; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset.x = s.x - offset.x / 2; + } + } else { + offset.x = s.x - offset.x; + } + } + + if (p_cell.flip_v) { + xform.elements[0].y = -xform.elements[0].y; + xform.elements[1].y = -xform.elements[1].y; + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT) { + offset.y = s.y - offset.y; + } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.y += s.y; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset.y += s.y; + } + } else { + offset.y = s.y - offset.y; + } + } + + if (centered_textures) { + offset += cell_size / 2 - s / 2; + } + xform.elements[2] += offset; +} + +void RTileMap::_add_shape(int &shape_idx, const Quadrant &p_q, const Ref &p_shape, const RTileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata) { + Physics2DServer *ps = Physics2DServer::get_singleton(); + + if (!use_parent) { + ps->body_add_shape(p_q.body, p_shape->get_rid(), p_xform); + ps->body_set_shape_metadata(p_q.body, shape_idx, p_metadata); + ps->body_set_shape_as_one_way_collision(p_q.body, shape_idx, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + + } else if (collision_parent) { + Transform2D xform = p_xform; + xform.set_origin(xform.get_origin() + p_q.pos); + + collision_parent->shape_owner_add_shape(p_q.shape_owner_id, p_shape); + + int real_index = collision_parent->shape_owner_get_shape_index(p_q.shape_owner_id, shape_idx); + RID rid = collision_parent->get_rid(); + + if (Object::cast_to(collision_parent) != nullptr) { + ps->area_set_shape_transform(rid, real_index, get_transform() * xform); + } else { + ps->body_set_shape_transform(rid, real_index, get_transform() * xform); + ps->body_set_shape_metadata(rid, real_index, p_metadata); + ps->body_set_shape_as_one_way_collision(rid, real_index, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + } + } + shape_idx++; +} + +void RTileMap::update_dirty_quadrants() { + if (!pending_update) { + return; + } + if (!is_inside_tree() || !tile_set.is_valid()) { + pending_update = false; + return; + } + + VisualServer *vs = VisualServer::get_singleton(); + Physics2DServer *ps = Physics2DServer::get_singleton(); + Vector2 tofs = get_cell_draw_offset(); + Transform2D nav_rel; + if (navigation) { + nav_rel = get_relative_transform_to_parent(navigation); + } + + Vector2 qofs; + + SceneTree *st = SceneTree::get_singleton(); + Color debug_collision_color; + Color debug_navigation_color; + + bool debug_shapes = false; + if (st) { + if (Engine::get_singleton()->is_editor_hint()) { + debug_shapes = show_collision; + } else { + debug_shapes = st->is_debugging_collisions_hint(); + } + + if (debug_shapes) { + debug_collision_color = st->get_debug_collisions_color(); + } + } + + bool debug_navigation = st && st->is_debugging_navigation_hint(); + if (debug_navigation) { + debug_navigation_color = st->get_debug_navigation_color(); + } + + while (dirty_quadrant_list.first()) { + Quadrant &q = *dirty_quadrant_list.first()->self(); + + for (List::Element *E = q.canvas_items.front(); E; E = E->next()) { + if (E->get().is_valid()) { + vs->free(E->get()); + } + } + q.canvas_items.clear(); + + if (!use_parent) { + ps->body_clear_shapes(q.body); + } else if (collision_parent) { + collision_parent->shape_owner_clear_shapes(q.shape_owner_id); + } + int shape_idx = 0; + + if (navigation) { + for (Map::Element *E = q.navpoly_ids.front(); E; E = E->next()) { + navigation->navpoly_remove(E->get().id); + } + q.navpoly_ids.clear(); + } + + for (Map::Element *E = q.occluder_instances.front(); E; E = E->next()) { + if (E->get().id.is_valid()) { + VS::get_singleton()->free(E->get().id); + } + } + q.occluder_instances.clear(); + Ref prev_material; + int prev_z_index = 0; + RID prev_canvas_item; + RID prev_debug_canvas_item; + + for (int i = 0; i < q.cells.size(); i++) { + Map::Element *E = tile_map.find(q.cells[i]); + Cell &c = E->get(); + //moment of truth + if (!tile_set->has_tile(c.id)) { + continue; + } + Ref tex = tile_set->tile_get_texture(c.id); + Vector2 tile_ofs = tile_set->tile_get_texture_offset(c.id); + + Vector2 wofs = _map_to_world(E->key().x, E->key().y); + Vector2 offset = wofs - q.pos + tofs; + + if (!tex.is_valid()) { + continue; + } + + Ref mat = tile_set->tile_get_material(c.id); + int z_index = tile_set->tile_get_z_index(c.id); + + if (tile_set->tile_get_tile_mode(c.id) == RTileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == RTileSet::ATLAS_TILE) { + z_index += tile_set->autotile_get_z_index(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); + } + + RID canvas_item; + RID debug_canvas_item; + + if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + canvas_item = RID_PRIME(vs->canvas_item_create()); + if (mat.is_valid()) { + vs->canvas_item_set_material(canvas_item, mat->get_rid()); + } + vs->canvas_item_set_parent(canvas_item, get_canvas_item()); + _update_item_material_state(canvas_item); + Transform2D xform; + xform.set_origin(q.pos); + vs->canvas_item_set_transform(canvas_item, xform); + vs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + vs->canvas_item_set_z_index(canvas_item, z_index); + + q.canvas_items.push_back(canvas_item); + + if (debug_shapes) { + debug_canvas_item = RID_PRIME(vs->canvas_item_create()); + vs->canvas_item_set_parent(debug_canvas_item, canvas_item); + vs->canvas_item_set_z_as_relative_to_parent(debug_canvas_item, false); + vs->canvas_item_set_z_index(debug_canvas_item, VS::CANVAS_ITEM_Z_MAX - 1); + q.canvas_items.push_back(debug_canvas_item); + prev_debug_canvas_item = debug_canvas_item; + } + + prev_canvas_item = canvas_item; + prev_material = mat; + prev_z_index = z_index; + + } else { + canvas_item = prev_canvas_item; + if (debug_shapes) { + debug_canvas_item = prev_debug_canvas_item; + } + } + + Rect2 r = tile_set->tile_get_region(c.id); + if (tile_set->tile_get_tile_mode(c.id) == RTileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == RTileSet::ATLAS_TILE) { + int spacing = tile_set->autotile_get_spacing(c.id); + r.size = tile_set->autotile_get_size(c.id); + r.position += (r.size + Vector2(spacing, spacing)) * Vector2(c.autotile_coord_x, c.autotile_coord_y); + } + + Size2 s; + if (r == Rect2()) { + s = tex->get_size(); + } else { + s = r.size; + } + + Rect2 rect; + rect.position = offset.floor(); + rect.size = s; + rect.size.x += fp_adjust; + rect.size.y += fp_adjust; + + if (compatibility_mode && !centered_textures) { + if (rect.size.y > rect.size.x) { + if ((c.flip_h && (c.flip_v || c.transpose)) || (c.flip_v && !c.transpose)) { + tile_ofs.y += rect.size.y - rect.size.x; + } + } else if (rect.size.y < rect.size.x) { + if ((c.flip_v && (c.flip_h || c.transpose)) || (c.flip_h && !c.transpose)) { + tile_ofs.x += rect.size.x - rect.size.y; + } + } + } + + if (c.transpose) { + SWAP(tile_ofs.x, tile_ofs.y); + if (centered_textures) { + rect.position.x += cell_size.x / 2 - rect.size.y / 2; + rect.position.y += cell_size.y / 2 - rect.size.x / 2; + } + } else if (centered_textures) { + rect.position += cell_size / 2 - rect.size / 2; + } + + if (c.flip_h) { + rect.size.x = -rect.size.x; + tile_ofs.x = -tile_ofs.x; + } + + if (c.flip_v) { + rect.size.y = -rect.size.y; + tile_ofs.y = -tile_ofs.y; + } + + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT) { + rect.position += tile_ofs; + + } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + rect.position += tile_ofs; + + if (c.transpose) { + if (c.flip_h) { + rect.position.x -= cell_size.x; + } else { + rect.position.x += cell_size.x; + } + } else { + if (c.flip_v) { + rect.position.y -= cell_size.y; + } else { + rect.position.y += cell_size.y; + } + } + + } else if (tile_origin == TILE_ORIGIN_CENTER) { + rect.position += tile_ofs; + + if (c.flip_h) { + rect.position.x -= cell_size.x / 2; + } else { + rect.position.x += cell_size.x / 2; + } + + if (c.flip_v) { + rect.position.y -= cell_size.y / 2; + } else { + rect.position.y += cell_size.y / 2; + } + } + } else { + rect.position += tile_ofs; + } + + Ref normal_map = tile_set->tile_get_normal_map(c.id); + Color modulate = tile_set->tile_get_modulate(c.id); + Color self_modulate = get_self_modulate(); + modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, + modulate.b * self_modulate.b, modulate.a * self_modulate.a); + if (r == Rect2()) { + tex->draw_rect(canvas_item, rect, false, modulate, c.transpose, normal_map); + } else { + tex->draw_rect_region(canvas_item, rect, r, modulate, c.transpose, normal_map, clip_uv); + } + + Vector shapes = tile_set->tile_get_shapes(c.id); + + for (int j = 0; j < shapes.size(); j++) { + Ref shape = shapes[j].shape; + if (shape.is_valid()) { + if (tile_set->tile_get_tile_mode(c.id) == RTileSet::SINGLE_TILE || (shapes[j].autotile_coord.x == c.autotile_coord_x && shapes[j].autotile_coord.y == c.autotile_coord_y)) { + Transform2D xform; + xform.set_origin(offset.floor()); + + Vector2 shape_ofs = shapes[j].shape_transform.get_origin(); + + _fix_cell_transform(xform, c, shape_ofs, s); + + xform *= shapes[j].shape_transform.untranslated(); + + if (debug_canvas_item.is_valid()) { + vs->canvas_item_add_set_transform(debug_canvas_item, xform); + shape->draw(debug_canvas_item, debug_collision_color); + } + + if (shape->has_meta("decomposed")) { + Array _shapes = shape->get_meta("decomposed"); + for (int k = 0; k < _shapes.size(); k++) { + Ref convex = _shapes[k]; + if (convex.is_valid()) { + _add_shape(shape_idx, q, convex, shapes[j], xform, Vector2(E->key().x, E->key().y)); +#ifdef DEBUG_ENABLED + } else { + print_error("The TileSet assigned to the RTileMap " + get_name() + " has an invalid convex shape."); +#endif + } + } + } else { + _add_shape(shape_idx, q, shape, shapes[j], xform, Vector2(E->key().x, E->key().y)); + } + } + } + } + + if (debug_canvas_item.is_valid()) { + vs->canvas_item_add_set_transform(debug_canvas_item, Transform2D()); + } + + if (navigation) { + Ref navpoly; + Vector2 npoly_ofs; + if (tile_set->tile_get_tile_mode(c.id) == RTileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == RTileSet::ATLAS_TILE) { + navpoly = tile_set->autotile_get_navigation_polygon(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); + npoly_ofs = Vector2(); + } else { + navpoly = tile_set->tile_get_navigation_polygon(c.id); + npoly_ofs = tile_set->tile_get_navigation_polygon_offset(c.id); + } + + if (navpoly.is_valid()) { + Transform2D xform; + xform.set_origin(offset.floor() + q.pos); + _fix_cell_transform(xform, c, npoly_ofs, s); + + int pid = navigation->navpoly_add(navpoly, nav_rel * xform); + + Quadrant::NavPoly np; + np.id = pid; + np.xform = xform; + q.navpoly_ids[E->key()] = np; + + if (debug_navigation) { + RID debug_navigation_item = RID_PRIME(vs->canvas_item_create()); + vs->canvas_item_set_parent(debug_navigation_item, canvas_item); + vs->canvas_item_set_z_as_relative_to_parent(debug_navigation_item, false); + vs->canvas_item_set_z_index(debug_navigation_item, VS::CANVAS_ITEM_Z_MAX - 2); // Display one below collision debug + + if (debug_navigation_item.is_valid()) { + PoolVector navigation_polygon_vertices = navpoly->get_vertices(); + int vsize = navigation_polygon_vertices.size(); + + if (vsize > 2) { + Vector colors; + Vector vertices; + vertices.resize(vsize); + colors.resize(vsize); + { + PoolVector::Read vr = navigation_polygon_vertices.read(); + for (int j = 0; j < vsize; j++) { + vertices.write[j] = vr[j]; + colors.write[j] = debug_navigation_color; + } + } + + Vector indices; + + for (int j = 0; j < navpoly->get_polygon_count(); j++) { + Vector polygon = navpoly->get_polygon(j); + + for (int k = 2; k < polygon.size(); k++) { + int kofs[3] = { 0, k - 1, k }; + for (int l = 0; l < 3; l++) { + int idx = polygon[kofs[l]]; + ERR_FAIL_INDEX(idx, vsize); + indices.push_back(idx); + } + } + } + Transform2D navxform; + navxform.set_origin(offset.floor()); + _fix_cell_transform(navxform, c, npoly_ofs, s); + + vs->canvas_item_set_transform(debug_navigation_item, navxform); + vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors); + } + } + } + } + } + + Ref occluder; + if (tile_set->tile_get_tile_mode(c.id) == RTileSet::AUTO_TILE || tile_set->tile_get_tile_mode(c.id) == RTileSet::ATLAS_TILE) { + occluder = tile_set->autotile_get_light_occluder(c.id, Vector2(c.autotile_coord_x, c.autotile_coord_y)); + } else { + occluder = tile_set->tile_get_light_occluder(c.id); + } + if (occluder.is_valid()) { + Vector2 occluder_ofs = tile_set->tile_get_occluder_offset(c.id); + Transform2D xform; + xform.set_origin(offset.floor() + q.pos); + _fix_cell_transform(xform, c, occluder_ofs, s); + + RID orid = RID_PRIME(VS::get_singleton()->canvas_light_occluder_create()); + VS::get_singleton()->canvas_light_occluder_set_transform(orid, get_global_transform() * xform); + VS::get_singleton()->canvas_light_occluder_set_polygon(orid, occluder->get_rid()); + VS::get_singleton()->canvas_light_occluder_attach_to_canvas(orid, get_canvas()); + VS::get_singleton()->canvas_light_occluder_set_light_mask(orid, occluder_light_mask); + VS::get_singleton()->canvas_light_occluder_set_enabled(orid, is_visible()); + Quadrant::Occluder oc; + oc.xform = xform; + oc.id = orid; + q.occluder_instances[E->key()] = oc; + } + } + + dirty_quadrant_list.remove(dirty_quadrant_list.first()); + quadrant_order_dirty = true; + } + + pending_update = false; + + if (quadrant_order_dirty) { + int index = -(int64_t)0x80000000; //always must be drawn below children + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + for (List::Element *F = q.canvas_items.front(); F; F = F->next()) { + VS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); + } + } + + quadrant_order_dirty = false; + } + + _recompute_rect_cache(); +} + +void RTileMap::_recompute_rect_cache() { +#ifdef DEBUG_ENABLED + + if (!rect_cache_dirty) { + return; + } + + Rect2 r_total; + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Rect2 r; + r.position = _map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size()); + r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size())); + r.expand_to(_map_to_world(E->key().x * _get_quadrant_size() + _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size())); + r.expand_to(_map_to_world(E->key().x * _get_quadrant_size(), E->key().y * _get_quadrant_size() + _get_quadrant_size())); + if (E == quadrant_map.front()) { + r_total = r; + } else { + r_total = r_total.merge(r); + } + } + + rect_cache = r_total; + + item_rect_changed(); + + rect_cache_dirty = false; +#endif +} + +Map::Element *RTileMap::_create_quadrant(const PosKey &p_qk) { + Transform2D xform; + //xform.set_origin(Point2(p_qk.x,p_qk.y)*cell_size*quadrant_size); + Quadrant q; + q.pos = _map_to_world(p_qk.x * _get_quadrant_size(), p_qk.y * _get_quadrant_size()); + q.pos += get_cell_draw_offset(); + if (tile_origin == TILE_ORIGIN_CENTER) { + q.pos += cell_size / 2; + } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + q.pos.y += cell_size.y; + } + + xform.set_origin(q.pos); + //q.canvas_item = VisualServer::get_singleton()->canvas_item_create(); + if (!use_parent) { + q.body = RID_PRIME(Physics2DServer::get_singleton()->body_create()); + Physics2DServer::get_singleton()->body_set_mode(q.body, use_kinematic ? Physics2DServer::BODY_MODE_KINEMATIC : Physics2DServer::BODY_MODE_STATIC); + + Physics2DServer::get_singleton()->body_attach_object_instance_id(q.body, get_instance_id()); + Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); + Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, friction); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, bounce); + + if (is_inside_tree()) { + xform = get_global_transform() * xform; + RID space = get_world_2d()->get_space(); + Physics2DServer::get_singleton()->body_set_space(q.body, space); + } + + Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + } else if (collision_parent) { + xform = get_transform() * xform; + q.shape_owner_id = collision_parent->create_shape_owner(this); + } else { + q.shape_owner_id = -1; + } + + rect_cache_dirty = true; + quadrant_order_dirty = true; + return quadrant_map.insert(p_qk, q); +} + +void RTileMap::_erase_quadrant(Map::Element *Q) { + Quadrant &q = Q->get(); + if (!use_parent) { + if (q.body.is_valid()) { + Physics2DServer::get_singleton()->free(q.body); + q.body = RID(); + } + } else if (collision_parent) { + collision_parent->remove_shape_owner(q.shape_owner_id); + } + + for (List::Element *E = q.canvas_items.front(); E; E = E->next()) { + if (E->get().is_valid()) { + VisualServer::get_singleton()->free(E->get()); + } + } + q.canvas_items.clear(); + if (q.dirty_list.in_list()) { + dirty_quadrant_list.remove(&q.dirty_list); + } + + if (navigation) { + for (Map::Element *E = q.navpoly_ids.front(); E; E = E->next()) { + navigation->navpoly_remove(E->get().id); + } + q.navpoly_ids.clear(); + } + + for (Map::Element *E = q.occluder_instances.front(); E; E = E->next()) { + if (E->get().id.is_valid()) { + VS::get_singleton()->free(E->get().id); + } + } + q.occluder_instances.clear(); + + quadrant_map.erase(Q); + rect_cache_dirty = true; +} + +void RTileMap::_make_quadrant_dirty(Map::Element *Q, bool update) { + Quadrant &q = Q->get(); + if (!q.dirty_list.in_list()) { + dirty_quadrant_list.add(&q.dirty_list); + } + + if (pending_update) { + return; + } + pending_update = true; + if (!is_inside_tree()) { + return; + } + + if (update) { + call_deferred("update_dirty_quadrants"); + } +} + +void RTileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { + set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); +} + +void RTileMap::_set_celld(const Vector2 &p_pos, const Dictionary &p_data) { + Variant v_pos_x = p_pos.x, v_pos_y = p_pos.y, v_tile = p_data["id"], v_flip_h = p_data["flip_h"], v_flip_v = p_data["flip_y"], v_transpose = p_data["transpose"], v_autotile_coord = p_data["auto_coord"]; + const Variant *args[7] = { &v_pos_x, &v_pos_y, &v_tile, &v_flip_h, &v_flip_v, &v_transpose, &v_autotile_coord }; + Variant::CallError ce; + call("set_cell", args, 7, ce); +} + +void RTileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) { + PosKey pk(p_x, p_y); + + Map::Element *E = tile_map.find(pk); + if (!E && p_tile == INVALID_CELL) { + return; //nothing to do + } + + PosKey qk = pk.to_quadrant(_get_quadrant_size()); + if (p_tile == INVALID_CELL) { + //erase existing + tile_map.erase(pk); + Map::Element *Q = quadrant_map.find(qk); + ERR_FAIL_COND(!Q); + Quadrant &q = Q->get(); + q.cells.erase(pk); + if (q.cells.size() == 0) { + _erase_quadrant(Q); + } else { + _make_quadrant_dirty(Q); + } + + used_size_cache_dirty = true; + return; + } + + Map::Element *Q = quadrant_map.find(qk); + + if (!E) { + E = tile_map.insert(pk, Cell()); + if (!Q) { + Q = _create_quadrant(qk); + } + Quadrant &q = Q->get(); + q.cells.insert(pk); + } else { + ERR_FAIL_COND(!Q); // quadrant should exist... + + if (E->get().id == p_tile && E->get().flip_h == p_flip_x && E->get().flip_v == p_flip_y && E->get().transpose == p_transpose && E->get().autotile_coord_x == (uint16_t)p_autotile_coord.x && E->get().autotile_coord_y == (uint16_t)p_autotile_coord.y) { + return; //nothing changed + } + } + + Cell &c = E->get(); + + c.id = p_tile; + c.flip_h = p_flip_x; + c.flip_v = p_flip_y; + c.transpose = p_transpose; + c.autotile_coord_x = (uint16_t)p_autotile_coord.x; + c.autotile_coord_y = (uint16_t)p_autotile_coord.y; + + _make_quadrant_dirty(Q); + used_size_cache_dirty = true; +} + +int RTileMap::get_cellv(const Vector2 &p_pos) const { + return get_cell(p_pos.x, p_pos.y); +} + +void RTileMap::make_bitmask_area_dirty(const Vector2 &p_pos) { + for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { + for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { + PosKey p(x, y); + if (dirty_bitmask.find(p) == nullptr) { + dirty_bitmask.push_back(p); + } + } + } +} + +void RTileMap::update_bitmask_area(const Vector2 &p_pos) { + for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { + for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { + update_cell_bitmask(x, y); + } + } +} + +void RTileMap::update_bitmask_region(const Vector2 &p_start, const Vector2 &p_end) { + if ((p_end.x < p_start.x || p_end.y < p_start.y) || (p_end.x == p_start.x && p_end.y == p_start.y)) { + Array a = get_used_cells(); + for (int i = 0; i < a.size(); i++) { + Vector2 vector = (Vector2)a[i]; + update_cell_bitmask(vector.x, vector.y); + } + return; + } + for (int x = p_start.x - 1; x <= p_end.x + 1; x++) { + for (int y = p_start.y - 1; y <= p_end.y + 1; y++) { + update_cell_bitmask(x, y); + } + } +} + +void RTileMap::update_cell_bitmask(int p_x, int p_y) { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot update cell bitmask if Tileset is not open."); + PosKey p(p_x, p_y); + Map::Element *E = tile_map.find(p); + if (E != nullptr) { + int id = get_cell(p_x, p_y); + if (!tile_set->has_tile(id)) { + return; + } + if (tile_set->tile_get_tile_mode(id) == RTileSet::AUTO_TILE) { + uint16_t mask = 0; + if (tile_set->autotile_get_bitmask_mode(id) == RTileSet::BITMASK_2X2) { + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= RTileSet::BIND_TOPLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= RTileSet::BIND_TOPRIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= RTileSet::BIND_BOTTOMLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= RTileSet::BIND_BOTTOMRIGHT; + } + } else { + if (tile_set->autotile_get_bitmask_mode(id) == RTileSet::BITMASK_3X3_MINIMAL) { + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= RTileSet::BIND_TOPLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= RTileSet::BIND_TOPRIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= RTileSet::BIND_BOTTOMLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= RTileSet::BIND_BOTTOMRIGHT; + } + } else { + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) { + mask |= RTileSet::BIND_TOPLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) { + mask |= RTileSet::BIND_TOPRIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) { + mask |= RTileSet::BIND_BOTTOMLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) { + mask |= RTileSet::BIND_BOTTOMRIGHT; + } + } + if (tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1))) { + mask |= RTileSet::BIND_TOP; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= RTileSet::BIND_LEFT; + } + mask |= RTileSet::BIND_CENTER; + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= RTileSet::BIND_RIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1))) { + mask |= RTileSet::BIND_BOTTOM; + } + } + Vector2 coord = tile_set->autotile_get_subtile_for_bitmask(id, mask, this, Vector2(p_x, p_y)); + E->get().autotile_coord_x = (int)coord.x; + E->get().autotile_coord_y = (int)coord.y; + + PosKey qk = p.to_quadrant(_get_quadrant_size()); + Map::Element *Q = quadrant_map.find(qk); + _make_quadrant_dirty(Q); + + } else if (tile_set->tile_get_tile_mode(id) == RTileSet::SINGLE_TILE) { + E->get().autotile_coord_x = 0; + E->get().autotile_coord_y = 0; + } else if (tile_set->tile_get_tile_mode(id) == RTileSet::ATLAS_TILE) { + if (tile_set->autotile_get_bitmask(id, Vector2(p_x, p_y)) == RTileSet::BIND_CENTER) { + Vector2 coord = tile_set->atlastile_get_subtile_by_priority(id, this, Vector2(p_x, p_y)); + + E->get().autotile_coord_x = (int)coord.x; + E->get().autotile_coord_y = (int)coord.y; + } + } + } +} + +void RTileMap::update_dirty_bitmask() { + while (dirty_bitmask.size() > 0) { + update_cell_bitmask(dirty_bitmask[0].x, dirty_bitmask[0].y); + dirty_bitmask.pop_front(); + } +} + +void RTileMap::fix_invalid_tiles() { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); + + Map temp_tile_map = tile_map; + for (Map::Element *E = temp_tile_map.front(); E; E = E->next()) { + if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) { + set_cell(E->key().x, E->key().y, INVALID_CELL); + } + } +} + +int RTileMap::get_cell(int p_x, int p_y) const { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return INVALID_CELL; + } + + return E->get().id; +} +bool RTileMap::is_cell_x_flipped(int p_x, int p_y) const { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return false; + } + + return E->get().flip_h; +} +bool RTileMap::is_cell_y_flipped(int p_x, int p_y) const { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return false; + } + + return E->get().flip_v; +} +bool RTileMap::is_cell_transposed(int p_x, int p_y) const { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return false; + } + + return E->get().transpose; +} + +void RTileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return; + } + + Cell c = E->get(); + c.autotile_coord_x = p_coord.x; + c.autotile_coord_y = p_coord.y; + tile_map[pk] = c; + + PosKey qk = pk.to_quadrant(_get_quadrant_size()); + Map::Element *Q = quadrant_map.find(qk); + + if (!Q) { + return; + } + + _make_quadrant_dirty(Q); +} + +Vector2 RTileMap::get_cell_autotile_coord(int p_x, int p_y) const { + PosKey pk(p_x, p_y); + + const Map::Element *E = tile_map.find(pk); + + if (!E) { + return Vector2(); + } + + return Vector2(E->get().autotile_coord_x, E->get().autotile_coord_y); +} + +void RTileMap::_recreate_quadrants() { + _clear_quadrants(); + + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + PosKey qk = PosKey(E->key().x, E->key().y).to_quadrant(_get_quadrant_size()); + + Map::Element *Q = quadrant_map.find(qk); + if (!Q) { + Q = _create_quadrant(qk); + dirty_quadrant_list.add(&Q->get().dirty_list); + } + + Q->get().cells.insert(E->key()); + _make_quadrant_dirty(Q, false); + } + update_dirty_quadrants(); +} + +void RTileMap::_clear_quadrants() { + while (quadrant_map.size()) { + _erase_quadrant(quadrant_map.front()); + } +} + +void RTileMap::set_material(const Ref &p_material) { + CanvasItem::set_material(p_material); + _update_all_items_material_state(); +} + +void RTileMap::set_use_parent_material(bool p_use_parent_material) { + CanvasItem::set_use_parent_material(p_use_parent_material); + _update_all_items_material_state(); +} + +void RTileMap::_update_all_items_material_state() { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + for (List::Element *F = q.canvas_items.front(); F; F = F->next()) { + _update_item_material_state(F->get()); + } + } +} + +void RTileMap::_update_item_material_state(const RID &p_canvas_item) { + VS::get_singleton()->canvas_item_set_use_parent_material(p_canvas_item, get_use_parent_material() || get_material().is_valid()); +} + +void RTileMap::clear() { + _clear_quadrants(); + tile_map.clear(); + used_size_cache_dirty = true; +} + +void RTileMap::_set_tile_data(const PoolVector &p_data) { + ERR_FAIL_COND(format > FORMAT_2); + + int c = p_data.size(); + PoolVector::Read r = p_data.read(); + + int offset = (format == FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); + + clear(); + for (int i = 0; i < c; i += offset) { + const uint8_t *ptr = (const uint8_t *)&r[i]; + uint8_t local[12]; + for (int j = 0; j < ((format == FORMAT_2) ? 12 : 8); j++) { + local[j] = ptr[j]; + } + +#ifdef BIG_ENDIAN_ENABLED + + SWAP(local[0], local[3]); + SWAP(local[1], local[2]); + SWAP(local[4], local[7]); + SWAP(local[5], local[6]); + //TODO: ask someone to check this... + if (FORMAT == FORMAT_2) { + SWAP(local[8], local[11]); + SWAP(local[9], local[10]); + } +#endif + + uint16_t x = decode_uint16(&local[0]); + uint16_t y = decode_uint16(&local[2]); + uint32_t v = decode_uint32(&local[4]); + bool flip_h = v & (1 << 29); + bool flip_v = v & (1 << 30); + bool transpose = v & (1 << 31); + v &= (1 << 29) - 1; + int16_t coord_x = 0; + int16_t coord_y = 0; + if (format == FORMAT_2) { + coord_x = decode_uint16(&local[8]); + coord_y = decode_uint16(&local[10]); + } + + set_cell(x, y, v, flip_h, flip_v, transpose, Vector2(coord_x, coord_y)); + } +} + +PoolVector RTileMap::_get_tile_data() const { + PoolVector data; + data.resize(tile_map.size() * 3); + PoolVector::Write w = data.write(); + + // Save in highest format + + int idx = 0; + for (const Map::Element *E = tile_map.front(); E; E = E->next()) { + uint8_t *ptr = (uint8_t *)&w[idx]; + encode_uint16(E->key().x, &ptr[0]); + encode_uint16(E->key().y, &ptr[2]); + uint32_t val = E->get().id; + if (E->get().flip_h) { + val |= (1 << 29); + } + if (E->get().flip_v) { + val |= (1 << 30); + } + if (E->get().transpose) { + val |= (1 << 31); + } + encode_uint32(val, &ptr[4]); + encode_uint16(E->get().autotile_coord_x, &ptr[8]); + encode_uint16(E->get().autotile_coord_y, &ptr[10]); + idx += 3; + } + + w.release(); + + return data; +} + +#ifdef TOOLS_ENABLED +Rect2 RTileMap::_edit_get_rect() const { + if (pending_update) { + const_cast(this)->update_dirty_quadrants(); + } else { + const_cast(this)->_recompute_rect_cache(); + } + return rect_cache; +} +#endif + +void RTileMap::set_collision_layer(uint32_t p_layer) { + collision_layer = p_layer; + if (!use_parent) { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); + } + } +} + +void RTileMap::set_collision_mask(uint32_t p_mask) { + collision_mask = p_mask; + if (!use_parent) { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); + } + } +} + +void RTileMap::set_collision_layer_bit(int p_bit, bool p_value) { + ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision layer bit must be between 0 and 31 inclusive."); + uint32_t layer = get_collision_layer(); + if (p_value) { + layer |= 1 << p_bit; + } else { + layer &= ~(1 << p_bit); + } + set_collision_layer(layer); +} + +void RTileMap::set_collision_mask_bit(int p_bit, bool p_value) { + ERR_FAIL_INDEX_MSG(p_bit, 32, "Collision mask bit must be between 0 and 31 inclusive."); + uint32_t mask = get_collision_mask(); + if (p_value) { + mask |= 1 << p_bit; + } else { + mask &= ~(1 << p_bit); + } + set_collision_mask(mask); +} + +bool RTileMap::get_collision_use_kinematic() const { + return use_kinematic; +} + +void RTileMap::set_collision_use_kinematic(bool p_use_kinematic) { + _clear_quadrants(); + use_kinematic = p_use_kinematic; + _recreate_quadrants(); +} + +bool RTileMap::get_collision_use_parent() const { + return use_parent; +} + +void RTileMap::set_collision_use_parent(bool p_use_parent) { + if (use_parent == p_use_parent) { + return; + } + + _clear_quadrants(); + + use_parent = p_use_parent; + set_notify_local_transform(use_parent); + + if (use_parent && is_inside_tree()) { + collision_parent = Object::cast_to(get_parent()); + } else { + collision_parent = nullptr; + } + + _recreate_quadrants(); + _change_notify(); + update_configuration_warning(); +} + +void RTileMap::set_collision_friction(float p_friction) { + friction = p_friction; + if (!use_parent) { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, p_friction); + } + } +} + +float RTileMap::get_collision_friction() const { + return friction; +} + +void RTileMap::set_collision_bounce(float p_bounce) { + bounce = p_bounce; + if (!use_parent) { + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, p_bounce); + } + } +} +float RTileMap::get_collision_bounce() const { + return bounce; +} + +uint32_t RTileMap::get_collision_layer() const { + return collision_layer; +} + +uint32_t RTileMap::get_collision_mask() const { + return collision_mask; +} + +bool RTileMap::get_collision_layer_bit(int p_bit) const { + ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision layer bit must be between 0 and 31 inclusive."); + return get_collision_layer() & (1 << p_bit); +} + +bool RTileMap::get_collision_mask_bit(int p_bit) const { + ERR_FAIL_INDEX_V_MSG(p_bit, 32, false, "Collision mask bit must be between 0 and 31 inclusive."); + return get_collision_mask() & (1 << p_bit); +} + +void RTileMap::set_mode(Mode p_mode) { + _clear_quadrants(); + mode = p_mode; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +RTileMap::Mode RTileMap::get_mode() const { + return mode; +} + +void RTileMap::set_half_offset(HalfOffset p_half_offset) { + _clear_quadrants(); + half_offset = p_half_offset; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +void RTileMap::set_tile_origin(TileOrigin p_tile_origin) { + _clear_quadrants(); + tile_origin = p_tile_origin; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +RTileMap::TileOrigin RTileMap::get_tile_origin() const { + return tile_origin; +} + +Vector2 RTileMap::get_cell_draw_offset() const { + switch (mode) { + case MODE_SQUARE: { + return Vector2(); + } break; + case MODE_ISOMETRIC: { + return Vector2(-cell_size.x * 0.5, 0); + + } break; + case MODE_CUSTOM: { + Vector2 min; + min.x = MIN(custom_transform[0].x, min.x); + min.y = MIN(custom_transform[0].y, min.y); + min.x = MIN(custom_transform[1].x, min.x); + min.y = MIN(custom_transform[1].y, min.y); + return min; + } break; + } + + return Vector2(); +} + +RTileMap::HalfOffset RTileMap::get_half_offset() const { + return half_offset; +} + +Transform2D RTileMap::get_cell_transform() const { + switch (mode) { + case MODE_SQUARE: { + Transform2D m; + m[0] *= cell_size.x; + m[1] *= cell_size.y; + return m; + } break; + case MODE_ISOMETRIC: { + //isometric only makes sense when y is positive in both x and y vectors, otherwise + //the drawing of tiles will overlap + Transform2D m; + m[0] = Vector2(cell_size.x * 0.5, cell_size.y * 0.5); + m[1] = Vector2(-cell_size.x * 0.5, cell_size.y * 0.5); + return m; + + } break; + case MODE_CUSTOM: { + return custom_transform; + } break; + } + + return Transform2D(); +} + +void RTileMap::set_custom_transform(const Transform2D &p_xform) { + _clear_quadrants(); + custom_transform = p_xform; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +Transform2D RTileMap::get_custom_transform() const { + return custom_transform; +} + +Vector2 RTileMap::_map_to_world(int p_x, int p_y, bool p_ignore_ofs) const { + Vector2 ret = get_cell_transform().xform(Vector2(p_x, p_y)); + if (!p_ignore_ofs) { + switch (half_offset) { + case HALF_OFFSET_X: + case HALF_OFFSET_NEGATIVE_X: { + if (ABS(p_y) & 1) { + ret += get_cell_transform()[0] * (half_offset == HALF_OFFSET_X ? 0.5 : -0.5); + } + } break; + case HALF_OFFSET_Y: + case HALF_OFFSET_NEGATIVE_Y: { + if (ABS(p_x) & 1) { + ret += get_cell_transform()[1] * (half_offset == HALF_OFFSET_Y ? 0.5 : -0.5); + } + } break; + case HALF_OFFSET_DISABLED: { + // Nothing to do. + } + } + } + return ret; +} + +bool RTileMap::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "format") { + if (p_value.get_type() == Variant::INT) { + format = (DataFormat)(p_value.operator int64_t()); // Set format used for loading + return true; + } + } else if (p_name == "tile_data") { + if (p_value.is_array()) { + _set_tile_data(p_value); + return true; + } + return false; + } + return false; +} + +bool RTileMap::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "format") { + r_ret = FORMAT_2; // When saving, always save highest format + return true; + } else if (p_name == "tile_data") { + r_ret = _get_tile_data(); + return true; + } + return false; +} + +void RTileMap::_get_property_list(List *p_list) const { + PropertyInfo p(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL); + p_list->push_back(p); + + p = PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL); + p_list->push_back(p); +} + +void RTileMap::_validate_property(PropertyInfo &property) const { + if (use_parent && property.name != "collision_use_parent" && property.name.begins_with("collision_")) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} + +Vector2 RTileMap::map_to_world(const Vector2 &p_pos, bool p_ignore_ofs) const { + return _map_to_world(p_pos.x, p_pos.y, p_ignore_ofs); +} + +Vector2 RTileMap::world_to_map(const Vector2 &p_pos) const { + Vector2 ret = get_cell_transform().affine_inverse().xform(p_pos); + + // Account for precision errors on the border (GH-23250). + // 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if + // cell size is > 15,000, but we can hardly have more precision anyway with + // floating point. + ret += Vector2(0.00005, 0.00005); + + switch (half_offset) { + case HALF_OFFSET_X: { + if (int(floor(ret.y)) & 1) { + ret.x -= 0.5; + } + } break; + case HALF_OFFSET_NEGATIVE_X: { + if (int(floor(ret.y)) & 1) { + ret.x += 0.5; + } + } break; + case HALF_OFFSET_Y: { + if (int(floor(ret.x)) & 1) { + ret.y -= 0.5; + } + } break; + case HALF_OFFSET_NEGATIVE_Y: { + if (int(floor(ret.x)) & 1) { + ret.y += 0.5; + } + } break; + case HALF_OFFSET_DISABLED: { + // Nothing to do. + } + } + + return ret.floor(); +} + +void RTileMap::set_y_sort_mode(bool p_enable) { + _clear_quadrants(); + y_sort_mode = p_enable; + VS::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), y_sort_mode); + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +bool RTileMap::is_y_sort_mode_enabled() const { + return y_sort_mode; +} + +void RTileMap::set_compatibility_mode(bool p_enable) { + _clear_quadrants(); + compatibility_mode = p_enable; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +bool RTileMap::is_compatibility_mode_enabled() const { + return compatibility_mode; +} + +void RTileMap::set_centered_textures(bool p_enable) { + _clear_quadrants(); + centered_textures = p_enable; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +bool RTileMap::is_centered_textures_enabled() const { + return centered_textures; +} + +Array RTileMap::get_used_cells() const { + Array a; + a.resize(tile_map.size()); + int i = 0; + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + Vector2 p(E->key().x, E->key().y); + a[i++] = p; + } + + return a; +} + +Array RTileMap::get_used_cells_by_id(int p_id) const { + Array a; + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + if (E->value().id == p_id) { + Vector2 p(E->key().x, E->key().y); + a.push_back(p); + } + } + + return a; +} + +Rect2 RTileMap::get_used_rect() { // Not const because of cache + + if (used_size_cache_dirty) { + if (tile_map.size() > 0) { + used_size_cache = Rect2(tile_map.front()->key().x, tile_map.front()->key().y, 0, 0); + + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + used_size_cache.expand_to(Vector2(E->key().x, E->key().y)); + } + + used_size_cache.size += Vector2(1, 1); + } else { + used_size_cache = Rect2(); + } + + used_size_cache_dirty = false; + } + + return used_size_cache; +} + +void RTileMap::set_occluder_light_mask(int p_mask) { + occluder_light_mask = p_mask; + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + for (Map::Element *F = E->get().occluder_instances.front(); F; F = F->next()) { + VisualServer::get_singleton()->canvas_light_occluder_set_light_mask(F->get().id, occluder_light_mask); + } + } +} + +int RTileMap::get_occluder_light_mask() const { + return occluder_light_mask; +} + +void RTileMap::set_light_mask(int p_light_mask) { + CanvasItem::set_light_mask(p_light_mask); + for (Map::Element *E = quadrant_map.front(); E; E = E->next()) { + for (List::Element *F = E->get().canvas_items.front(); F; F = F->next()) { + VisualServer::get_singleton()->canvas_item_set_light_mask(F->get(), get_light_mask()); + } + } +} + +void RTileMap::set_clip_uv(bool p_enable) { + if (clip_uv == p_enable) { + return; + } + + _clear_quadrants(); + clip_uv = p_enable; + _recreate_quadrants(); +} + +bool RTileMap::get_clip_uv() const { + return clip_uv; +} + +String RTileMap::get_configuration_warning() const { + String warning = Node2D::get_configuration_warning(); + + if (use_parent && !collision_parent) { + if (!warning.empty()) { + warning += "\n\n"; + } + return TTR("RTileMap with Use Parent on needs a parent CollisionObject2D to give shapes to. Please use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); + } + + return warning; +} + +void RTileMap::set_show_collision(bool p_value) { + show_collision = p_value; + _recreate_quadrants(); +} + +bool RTileMap::is_show_collision_enabled() const { + return show_collision; +} + +void RTileMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &RTileMap::set_tileset); + ClassDB::bind_method(D_METHOD("get_tileset"), &RTileMap::get_tileset); + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &RTileMap::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &RTileMap::get_mode); + + ClassDB::bind_method(D_METHOD("set_half_offset", "half_offset"), &RTileMap::set_half_offset); + ClassDB::bind_method(D_METHOD("get_half_offset"), &RTileMap::get_half_offset); + + ClassDB::bind_method(D_METHOD("set_custom_transform", "custom_transform"), &RTileMap::set_custom_transform); + ClassDB::bind_method(D_METHOD("get_custom_transform"), &RTileMap::get_custom_transform); + + ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RTileMap::set_cell_size); + ClassDB::bind_method(D_METHOD("get_cell_size"), &RTileMap::get_cell_size); + + ClassDB::bind_method(D_METHOD("_set_old_cell_size", "size"), &RTileMap::_set_old_cell_size); + ClassDB::bind_method(D_METHOD("_get_old_cell_size"), &RTileMap::_get_old_cell_size); + + ClassDB::bind_method(D_METHOD("set_quadrant_size", "size"), &RTileMap::set_quadrant_size); + ClassDB::bind_method(D_METHOD("get_quadrant_size"), &RTileMap::get_quadrant_size); + + ClassDB::bind_method(D_METHOD("set_tile_origin", "origin"), &RTileMap::set_tile_origin); + ClassDB::bind_method(D_METHOD("get_tile_origin"), &RTileMap::get_tile_origin); + + ClassDB::bind_method(D_METHOD("set_clip_uv", "enable"), &RTileMap::set_clip_uv); + ClassDB::bind_method(D_METHOD("get_clip_uv"), &RTileMap::get_clip_uv); + + ClassDB::bind_method(D_METHOD("set_y_sort_mode", "enable"), &RTileMap::set_y_sort_mode); + ClassDB::bind_method(D_METHOD("is_y_sort_mode_enabled"), &RTileMap::is_y_sort_mode_enabled); + + ClassDB::bind_method(D_METHOD("set_compatibility_mode", "enable"), &RTileMap::set_compatibility_mode); + ClassDB::bind_method(D_METHOD("is_compatibility_mode_enabled"), &RTileMap::is_compatibility_mode_enabled); + + ClassDB::bind_method(D_METHOD("set_show_collision", "enable"), &RTileMap::set_show_collision); + ClassDB::bind_method(D_METHOD("is_show_collision_enabled"), &RTileMap::is_show_collision_enabled); + + ClassDB::bind_method(D_METHOD("set_centered_textures", "enable"), &RTileMap::set_centered_textures); + ClassDB::bind_method(D_METHOD("is_centered_textures_enabled"), &RTileMap::is_centered_textures_enabled); + + ClassDB::bind_method(D_METHOD("set_collision_use_kinematic", "use_kinematic"), &RTileMap::set_collision_use_kinematic); + ClassDB::bind_method(D_METHOD("get_collision_use_kinematic"), &RTileMap::get_collision_use_kinematic); + + ClassDB::bind_method(D_METHOD("set_collision_use_parent", "use_parent"), &RTileMap::set_collision_use_parent); + ClassDB::bind_method(D_METHOD("get_collision_use_parent"), &RTileMap::get_collision_use_parent); + + ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &RTileMap::set_collision_layer); + ClassDB::bind_method(D_METHOD("get_collision_layer"), &RTileMap::get_collision_layer); + + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &RTileMap::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &RTileMap::get_collision_mask); + + ClassDB::bind_method(D_METHOD("set_collision_layer_bit", "bit", "value"), &RTileMap::set_collision_layer_bit); + ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &RTileMap::get_collision_layer_bit); + + ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &RTileMap::set_collision_mask_bit); + ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &RTileMap::get_collision_mask_bit); + + ClassDB::bind_method(D_METHOD("set_collision_friction", "value"), &RTileMap::set_collision_friction); + ClassDB::bind_method(D_METHOD("get_collision_friction"), &RTileMap::get_collision_friction); + + ClassDB::bind_method(D_METHOD("set_collision_bounce", "value"), &RTileMap::set_collision_bounce); + ClassDB::bind_method(D_METHOD("get_collision_bounce"), &RTileMap::get_collision_bounce); + + ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &RTileMap::set_occluder_light_mask); + ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &RTileMap::get_occluder_light_mask); + + ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &RTileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2())); + ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &RTileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_celld", "position", "data"), &RTileMap::_set_celld); + ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &RTileMap::get_cell); + ClassDB::bind_method(D_METHOD("get_cellv", "position"), &RTileMap::get_cellv); + ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &RTileMap::is_cell_x_flipped); + ClassDB::bind_method(D_METHOD("is_cell_y_flipped", "x", "y"), &RTileMap::is_cell_y_flipped); + ClassDB::bind_method(D_METHOD("is_cell_transposed", "x", "y"), &RTileMap::is_cell_transposed); + + ClassDB::bind_method(D_METHOD("get_cell_autotile_coord", "x", "y"), &RTileMap::get_cell_autotile_coord); + + ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &RTileMap::fix_invalid_tiles); + ClassDB::bind_method(D_METHOD("clear"), &RTileMap::clear); + + ClassDB::bind_method(D_METHOD("get_used_cells"), &RTileMap::get_used_cells); + ClassDB::bind_method(D_METHOD("get_used_cells_by_id", "id"), &RTileMap::get_used_cells_by_id); + ClassDB::bind_method(D_METHOD("get_used_rect"), &RTileMap::get_used_rect); + + ClassDB::bind_method(D_METHOD("map_to_world", "map_position", "ignore_half_ofs"), &RTileMap::map_to_world, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("world_to_map", "world_position"), &RTileMap::world_to_map); + + ClassDB::bind_method(D_METHOD("_clear_quadrants"), &RTileMap::_clear_quadrants); + ClassDB::bind_method(D_METHOD("_recreate_quadrants"), &RTileMap::_recreate_quadrants); + ClassDB::bind_method(D_METHOD("update_dirty_quadrants"), &RTileMap::update_dirty_quadrants); + + ClassDB::bind_method(D_METHOD("update_bitmask_area", "position"), &RTileMap::update_bitmask_area); + ClassDB::bind_method(D_METHOD("update_bitmask_region", "start", "end"), &RTileMap::update_bitmask_region, DEFVAL(Vector2()), DEFVAL(Vector2())); + + ClassDB::bind_method(D_METHOD("_set_tile_data"), &RTileMap::_set_tile_data); + ClassDB::bind_method(D_METHOD("_get_tile_data"), &RTileMap::_get_tile_data); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Square,Isometric,Custom"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "RTileSet"), "set_tileset", "get_tileset"); + + ADD_GROUP("Cell", "cell_"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size", PROPERTY_HINT_RANGE, "1,8192,1"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "cell_custom_transform"), "set_custom_transform", "get_custom_transform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_half_offset", PROPERTY_HINT_ENUM, "Offset X,Offset Y,Disabled,Offset Negative X,Offset Negative Y"), "set_half_offset", "get_half_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_tile_origin", PROPERTY_HINT_ENUM, "Top Left,Center,Bottom Left"), "set_tile_origin", "get_tile_origin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_y_sort"), "set_y_sort_mode", "is_y_sort_mode_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_collision"), "set_show_collision", "is_show_collision_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compatibility_mode"), "set_compatibility_mode", "is_compatibility_mode_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered_textures"), "set_centered_textures", "is_centered_textures_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_clip_uv"), "set_clip_uv", "get_clip_uv"); + + ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_parent", PROPERTY_HINT_NONE, ""), "set_collision_use_parent", "get_collision_use_parent"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_kinematic", PROPERTY_HINT_NONE, ""), "set_collision_use_kinematic", "get_collision_use_kinematic"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); + + ADD_GROUP("Occluder", "occluder_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); + + ADD_PROPERTY_DEFAULT("format", FORMAT_1); + + ADD_SIGNAL(MethodInfo("settings_changed")); + + BIND_CONSTANT(INVALID_CELL); + + BIND_ENUM_CONSTANT(MODE_SQUARE); + BIND_ENUM_CONSTANT(MODE_ISOMETRIC); + BIND_ENUM_CONSTANT(MODE_CUSTOM); + + BIND_ENUM_CONSTANT(HALF_OFFSET_X); + BIND_ENUM_CONSTANT(HALF_OFFSET_Y); + BIND_ENUM_CONSTANT(HALF_OFFSET_DISABLED); + BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_X); + BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_Y); + + BIND_ENUM_CONSTANT(TILE_ORIGIN_TOP_LEFT); + BIND_ENUM_CONSTANT(TILE_ORIGIN_CENTER); + BIND_ENUM_CONSTANT(TILE_ORIGIN_BOTTOM_LEFT); +} + +void RTileMap::_changed_callback(Object *p_changed, const char *p_prop) { + if (tile_set.is_valid() && tile_set.ptr() == p_changed) { + emit_signal("settings_changed"); + } +} + +RTileMap::RTileMap() { + rect_cache_dirty = true; + used_size_cache_dirty = true; + pending_update = false; + quadrant_order_dirty = false; + quadrant_size = 16; + cell_size = Size2(64, 64); + custom_transform = Transform2D(64, 0, 0, 64, 0, 0); + collision_layer = 1; + collision_mask = 1; + friction = 1; + bounce = 0; + mode = MODE_SQUARE; + half_offset = HALF_OFFSET_DISABLED; + use_parent = false; + collision_parent = nullptr; + use_kinematic = false; + navigation = nullptr; + y_sort_mode = false; + compatibility_mode = false; + centered_textures = false; + occluder_light_mask = 1; + clip_uv = false; + format = FORMAT_1; // Assume lowest possible format if none is present + + fp_adjust = 0.00001; + tile_origin = TILE_ORIGIN_TOP_LEFT; + set_notify_transform(true); + set_notify_local_transform(false); +} + +RTileMap::~RTileMap() { + if (tile_set.is_valid()) { + tile_set->remove_change_receptor(this); + } + + clear(); +} diff --git a/tile_map.h b/tile_map.h new file mode 100644 index 0000000..8e5bb8e --- /dev/null +++ b/tile_map.h @@ -0,0 +1,360 @@ +/*************************************************************************/ +/* tile_map.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RTILE_MAP_H +#define RTILE_MAP_H + +#include "core/self_list.h" +#include "core/vset.h" +#include "scene/2d/navigation_2d.h" +#include "scene/2d/node_2d.h" +#include "tile_set.h" + +class CollisionObject2D; + +class RTileMap : public Node2D { + GDCLASS(RTileMap, Node2D); + +public: + enum Mode { + MODE_SQUARE, + MODE_ISOMETRIC, + MODE_CUSTOM + }; + + enum HalfOffset { + HALF_OFFSET_X, + HALF_OFFSET_Y, + HALF_OFFSET_DISABLED, + HALF_OFFSET_NEGATIVE_X, + HALF_OFFSET_NEGATIVE_Y, + }; + + enum TileOrigin { + TILE_ORIGIN_TOP_LEFT, + TILE_ORIGIN_CENTER, + TILE_ORIGIN_BOTTOM_LEFT + }; + +private: + enum DataFormat { + FORMAT_1 = 0, + FORMAT_2 + }; + + Ref tile_set; + Size2i cell_size; + int quadrant_size; + Mode mode; + Transform2D custom_transform; + HalfOffset half_offset; + bool use_parent; + CollisionObject2D *collision_parent; + bool use_kinematic; + Navigation2D *navigation; + bool show_collision = false; + + union PosKey { + struct { + int16_t x; + int16_t y; + }; + uint32_t key; + + //using a more precise comparison so the regions can be sorted later + bool operator<(const PosKey &p_k) const { return (y == p_k.y) ? x < p_k.x : y < p_k.y; } + + bool operator==(const PosKey &p_k) const { return (y == p_k.y && x == p_k.x); } + + PosKey to_quadrant(const int &p_quadrant_size) const { + // rounding down, instead of simply rounding towards zero (truncating) + return PosKey( + x > 0 ? x / p_quadrant_size : (x - (p_quadrant_size - 1)) / p_quadrant_size, + y > 0 ? y / p_quadrant_size : (y - (p_quadrant_size - 1)) / p_quadrant_size); + } + + PosKey(int16_t p_x, int16_t p_y) { + x = p_x; + y = p_y; + } + PosKey() { + x = 0; + y = 0; + } + }; + + union Cell { + struct { + int32_t id : 24; + bool flip_h : 1; + bool flip_v : 1; + bool transpose : 1; + int16_t autotile_coord_x : 16; + int16_t autotile_coord_y : 16; + }; + + uint64_t _u64t; + Cell() { _u64t = 0; } + }; + + Map tile_map; + List dirty_bitmask; + + struct Quadrant { + Vector2 pos; + List canvas_items; + RID body; + uint32_t shape_owner_id; + + SelfList dirty_list; + + struct NavPoly { + int id; + Transform2D xform; + }; + + struct Occluder { + RID id; + Transform2D xform; + }; + + Map navpoly_ids; + Map occluder_instances; + + VSet cells; + + void operator=(const Quadrant &q) { + pos = q.pos; + canvas_items = q.canvas_items; + body = q.body; + shape_owner_id = q.shape_owner_id; + cells = q.cells; + navpoly_ids = q.navpoly_ids; + occluder_instances = q.occluder_instances; + } + Quadrant(const Quadrant &q) : + dirty_list(this) { + pos = q.pos; + canvas_items = q.canvas_items; + body = q.body; + shape_owner_id = q.shape_owner_id; + cells = q.cells; + occluder_instances = q.occluder_instances; + navpoly_ids = q.navpoly_ids; + } + Quadrant() : + dirty_list(this) {} + }; + + Map quadrant_map; + + SelfList::List dirty_quadrant_list; + + bool pending_update; + + Rect2 rect_cache; + bool rect_cache_dirty; + Rect2 used_size_cache; + bool used_size_cache_dirty; + bool quadrant_order_dirty; + bool y_sort_mode; + bool compatibility_mode; + bool centered_textures; + bool clip_uv; + float fp_adjust; + float friction; + float bounce; + uint32_t collision_layer; + uint32_t collision_mask; + mutable DataFormat format; + + TileOrigin tile_origin; + + int occluder_light_mask; + + void _fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc); + + void _add_shape(int &shape_idx, const Quadrant &p_q, const Ref &p_shape, const RTileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata); + + Map::Element *_create_quadrant(const PosKey &p_qk); + void _erase_quadrant(Map::Element *Q); + void _make_quadrant_dirty(Map::Element *Q, bool update = true); + void _recreate_quadrants(); + void _clear_quadrants(); + void _update_quadrant_space(const RID &p_space); + void _update_quadrant_transform(); + void _recompute_rect_cache(); + + void _update_all_items_material_state(); + _FORCE_INLINE_ void _update_item_material_state(const RID &p_canvas_item); + + _FORCE_INLINE_ int _get_quadrant_size() const; + + void _set_tile_data(const PoolVector &p_data); + PoolVector _get_tile_data() const; + + void _set_old_cell_size(int p_size) { set_cell_size(Size2(p_size, p_size)); } + int _get_old_cell_size() const { return cell_size.x; } + + _FORCE_INLINE_ Vector2 _map_to_world(int p_x, int p_y, bool p_ignore_ofs = false) const; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + void _notification(int p_what); + static void _bind_methods(); + + virtual void _validate_property(PropertyInfo &property) const; + virtual void _changed_callback(Object *p_changed, const char *p_prop); + +public: + enum { + INVALID_CELL = -1 + }; + +#ifdef TOOLS_ENABLED + virtual Rect2 _edit_get_rect() const; +#endif + + void set_tileset(const Ref &p_tileset); + Ref get_tileset() const; + + void set_cell_size(Size2 p_size); + Size2 get_cell_size() const; + + void set_quadrant_size(int p_size); + int get_quadrant_size() const; + + void set_cell(int p_x, int p_y, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false, Vector2 p_autotile_coord = Vector2()); + int get_cell(int p_x, int p_y) const; + bool is_cell_x_flipped(int p_x, int p_y) const; + bool is_cell_y_flipped(int p_x, int p_y) const; + bool is_cell_transposed(int p_x, int p_y) const; + void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord); + Vector2 get_cell_autotile_coord(int p_x, int p_y) const; + + void _set_celld(const Vector2 &p_pos, const Dictionary &p_data); + void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false); + int get_cellv(const Vector2 &p_pos) const; + + void make_bitmask_area_dirty(const Vector2 &p_pos); + void update_bitmask_area(const Vector2 &p_pos); + void update_bitmask_region(const Vector2 &p_start = Vector2(), const Vector2 &p_end = Vector2()); + void update_cell_bitmask(int p_x, int p_y); + void update_dirty_bitmask(); + + void update_dirty_quadrants(); + + void set_show_collision(bool p_value); + bool is_show_collision_enabled() const; + + void set_collision_layer(uint32_t p_layer); + uint32_t get_collision_layer() const; + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() const; + + void set_collision_layer_bit(int p_bit, bool p_value); + bool get_collision_layer_bit(int p_bit) const; + + void set_collision_mask_bit(int p_bit, bool p_value); + bool get_collision_mask_bit(int p_bit) const; + + void set_collision_use_kinematic(bool p_use_kinematic); + bool get_collision_use_kinematic() const; + + void set_collision_use_parent(bool p_use_parent); + bool get_collision_use_parent() const; + + void set_collision_friction(float p_friction); + float get_collision_friction() const; + + void set_collision_bounce(float p_bounce); + float get_collision_bounce() const; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + void set_half_offset(HalfOffset p_half_offset); + HalfOffset get_half_offset() const; + + void set_tile_origin(TileOrigin p_tile_origin); + TileOrigin get_tile_origin() const; + + void set_custom_transform(const Transform2D &p_xform); + Transform2D get_custom_transform() const; + + Transform2D get_cell_transform() const; + Vector2 get_cell_draw_offset() const; + + Vector2 map_to_world(const Vector2 &p_pos, bool p_ignore_ofs = false) const; + Vector2 world_to_map(const Vector2 &p_pos) const; + + void set_y_sort_mode(bool p_enable); + bool is_y_sort_mode_enabled() const; + + void set_compatibility_mode(bool p_enable); + bool is_compatibility_mode_enabled() const; + + void set_centered_textures(bool p_enable); + bool is_centered_textures_enabled() const; + + Array get_used_cells() const; + Array get_used_cells_by_id(int p_id) const; + Rect2 get_used_rect(); // Not const because of cache + + void set_occluder_light_mask(int p_mask); + int get_occluder_light_mask() const; + + virtual void set_light_mask(int p_light_mask); + + virtual void set_material(const Ref &p_material); + + virtual void set_use_parent_material(bool p_use_parent_material); + + void set_clip_uv(bool p_enable); + bool get_clip_uv() const; + + String get_configuration_warning() const; + + void fix_invalid_tiles(); + void clear(); + + RTileMap(); + ~RTileMap(); +}; + +VARIANT_ENUM_CAST(RTileMap::Mode); +VARIANT_ENUM_CAST(RTileMap::HalfOffset); +VARIANT_ENUM_CAST(RTileMap::TileOrigin); + +#endif // TILE_MAP_H diff --git a/tile_map_editor_plugin.cpp b/tile_map_editor_plugin.cpp new file mode 100644 index 0000000..e151a8c --- /dev/null +++ b/tile_map_editor_plugin.cpp @@ -0,0 +1,2195 @@ +/*************************************************************************/ +/* tile_map_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_map_editor_plugin.h" + +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "core/math/math_funcs.h" +#include "core/os/input.h" +#include "core/os/keyboard.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "scene/gui/split_container.h" + +void RTileMapEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +void RTileMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PROCESS: { + if (bucket_queue.size()) { + CanvasItemEditor::get_singleton()->update_viewport(); + } + + } break; + + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", this, "_node_removed"); + FALLTHROUGH; + } + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (is_visible_in_tree()) { + _update_palette(); + } + + paint_button->set_icon(get_icon("Edit", "EditorIcons")); + bucket_fill_button->set_icon(get_icon("Bucket", "EditorIcons")); + picker_button->set_icon(get_icon("ColorPick", "EditorIcons")); + select_button->set_icon(get_icon("ActionCopy", "EditorIcons")); + + rotate_left_button->set_icon(get_icon("RotateLeft", "EditorIcons")); + rotate_right_button->set_icon(get_icon("RotateRight", "EditorIcons")); + flip_horizontal_button->set_icon(get_icon("MirrorX", "EditorIcons")); + flip_vertical_button->set_icon(get_icon("MirrorY", "EditorIcons")); + clear_transform_button->set_icon(get_icon("Clear", "EditorIcons")); + + search_box->set_right_icon(get_icon("Search", "EditorIcons")); + search_box->set_clear_button_enabled(true); + + PopupMenu *p = options->get_popup(); + p->set_item_icon(p->get_item_index(OPTION_CUT), get_icon("ActionCut", "EditorIcons")); + p->set_item_icon(p->get_item_index(OPTION_COPY), get_icon("Duplicate", "EditorIcons")); + p->set_item_icon(p->get_item_index(OPTION_ERASE_SELECTION), get_icon("Remove", "EditorIcons")); + + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", this, "_node_removed"); + } break; + + case NOTIFICATION_WM_FOCUS_OUT: { + if (tool == TOOL_PAINTING) { + Vector ids = get_selected_tiles(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + _set_cell(over_tile, ids, flip_h, flip_v, transpose); + _finish_undo(); + + paint_undo.clear(); + } + + tool = TOOL_NONE; + _update_button_tool(); + } + + // set flag to ignore over_tile on refocus + refocus_over_tile = true; + } break; + } +} + +void RTileMapEditor::_update_button_tool() { + ToolButton *tb[4] = { paint_button, bucket_fill_button, picker_button, select_button }; + // Unpress all buttons + for (int i = 0; i < 4; i++) { + tb[i]->set_pressed(false); + } + + // Press the good button + switch (tool) { + case TOOL_NONE: + case TOOL_PAINTING: { + paint_button->set_pressed(true); + } break; + case TOOL_BUCKET: { + bucket_fill_button->set_pressed(true); + } break; + case TOOL_PICKING: { + picker_button->set_pressed(true); + } break; + case TOOL_SELECTING: { + select_button->set_pressed(true); + } break; + default: + break; + } + + if (tool != TOOL_PICKING) { + last_tool = tool; + } +} + +void RTileMapEditor::_button_tool_select(int p_tool) { + tool = (Tool)p_tool; + _update_button_tool(); + switch (tool) { + case TOOL_SELECTING: { + selection_active = false; + } break; + default: + break; + } + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void RTileMapEditor::_menu_option(int p_option) { + switch (p_option) { + case OPTION_COPY: { + _update_copydata(); + + if (selection_active) { + tool = TOOL_PASTING; + + CanvasItemEditor::get_singleton()->update_viewport(); + } + } break; + case OPTION_ERASE_SELECTION: { + if (!selection_active) { + return; + } + + _start_undo(TTR("Erase Selection")); + _erase_selection(); + _finish_undo(); + + selection_active = false; + copydata.clear(); + + CanvasItemEditor::get_singleton()->update_viewport(); + } break; + case OPTION_FIX_INVALID: { + undo_redo->create_action(TTR("Fix Invalid Tiles")); + undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + node->fix_invalid_tiles(); + undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); + undo_redo->commit_action(); + + } break; + case OPTION_CUT: { + if (selection_active) { + _update_copydata(); + + _start_undo(TTR("Cut Selection")); + _erase_selection(); + _finish_undo(); + + selection_active = false; + + tool = TOOL_PASTING; + + CanvasItemEditor::get_singleton()->update_viewport(); + } + } break; + } + _update_button_tool(); +} + +void RTileMapEditor::_palette_selected(int index) { + _update_palette(); +} + +void RTileMapEditor::_palette_multi_selected(int index, bool selected) { + _update_palette(); +} + +void RTileMapEditor::_palette_input(const Ref &p_event) { + const Ref mb = p_event; + + // Zoom in/out using Ctrl + mouse wheel. + if (mb.is_valid() && mb->is_pressed() && mb->get_command()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { + size_slider->set_value(size_slider->get_value() + 0.2); + } + + if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + size_slider->set_value(size_slider->get_value() - 0.2); + } + } +} + +void RTileMapEditor::_canvas_mouse_enter() { + mouse_over = true; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void RTileMapEditor::_canvas_mouse_exit() { + mouse_over = false; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +Vector RTileMapEditor::get_selected_tiles() const { + Vector items = palette->get_selected_items(); + + if (items.size() == 0) { + items.push_back(RTileMap::INVALID_CELL); + return items; + } + + for (int i = items.size() - 1; i >= 0; i--) { + items.write[i] = palette->get_item_metadata(items[i]); + } + return items; +} + +void RTileMapEditor::set_selected_tiles(Vector p_tiles) { + palette->unselect_all(); + + for (int i = p_tiles.size() - 1; i >= 0; i--) { + int idx = palette->find_metadata(p_tiles[i]); + + if (idx >= 0) { + palette->select(idx, false); + } + } + + palette->ensure_current_is_visible(); +} + +Dictionary RTileMapEditor::_create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord) { + Dictionary cell; + + cell["id"] = tile; + cell["flip_h"] = flip_x; + cell["flip_y"] = flip_y; + cell["transpose"] = transpose; + cell["auto_coord"] = autotile_coord; + + return cell; +} + +void RTileMapEditor::_create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) { + Dictionary cell_old = _create_cell_dictionary(p_cell_old.idx, p_cell_old.xf, p_cell_old.yf, p_cell_old.tr, p_cell_old.ac); + Dictionary cell_new = _create_cell_dictionary(p_cell_new.idx, p_cell_new.xf, p_cell_new.yf, p_cell_new.tr, p_cell_new.ac); + + undo_redo->add_undo_method(node, "_set_celld", p_vec, cell_old); + undo_redo->add_do_method(node, "_set_celld", p_vec, cell_new); +} + +void RTileMapEditor::_start_undo(const String &p_action) { + undo_data.clear(); + undo_redo->create_action(p_action); +} + +void RTileMapEditor::_finish_undo() { + if (undo_data.size()) { + for (Map::Element *E = undo_data.front(); E; E = E->next()) { + _create_set_cell_undo_redo(E->key(), E->get(), _get_op_from_cell(E->key())); + } + + undo_data.clear(); + } + + undo_redo->commit_action(); +} + +void RTileMapEditor::_set_cell(const Point2i &p_pos, Vector p_values, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord) { + ERR_FAIL_COND(!node); + + if (p_values.size() == 0) { + return; + } + + int p_value = p_values[Math::rand() % p_values.size()]; + int prev_val = node->get_cell(p_pos.x, p_pos.y); + + bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); + bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); + bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y); + Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y); + + Vector2 position; + int current = manual_palette->get_current(); + if (current != -1) { + if (tool != TOOL_PASTING) { + position = manual_palette->get_item_metadata(current); + } else { + position = p_autotile_coord; + } + } else { + // If there is no manual tile selected, that either means that + // autotiling is enabled, or the given tile is not autotiling. Either + // way, the coordinate of the tile does not matter, so assigning it to + // the coordinate of the existing tile works fine. + position = prev_position; + } + + if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position) { + return; // Check that it's actually different. + } + + for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { + for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { + Point2i p = Point2i(x, y); + if (!undo_data.has(p)) { + undo_data[p] = _get_op_from_cell(p); + } + } + } + + node->_set_celld(p_pos, _create_cell_dictionary(p_value, p_flip_h, p_flip_v, p_transpose, p_autotile_coord)); + + if (tool == TOOL_PASTING) { + return; + } + + if (manual_autotile || (p_value != -1 && node->get_tileset()->has_tile(p_value) && node->get_tileset()->tile_get_tile_mode(p_value) == RTileSet::ATLAS_TILE)) { + if (current != -1) { + node->set_cell_autotile_coord(p_pos.x, p_pos.y, position); + } else if (node->get_tileset()->tile_get_tile_mode(p_value) == RTileSet::ATLAS_TILE && priority_atlastile) { + // BIND_CENTER is used to indicate that bitmask should not update for this tile cell. + node->get_tileset()->autotile_set_bitmask(p_value, Vector2(p_pos.x, p_pos.y), RTileSet::BIND_CENTER); + node->update_cell_bitmask(p_pos.x, p_pos.y); + } + } else { + node->update_bitmask_area(Point2(p_pos)); + } +} + +void RTileMapEditor::_manual_toggled(bool p_enabled) { + manual_autotile = p_enabled; + _update_palette(); +} + +void RTileMapEditor::_priority_toggled(bool p_enabled) { + priority_atlastile = p_enabled; + _update_palette(); +} + +void RTileMapEditor::_text_entered(const String &p_text) { + canvas_item_editor_viewport->grab_focus(); +} + +void RTileMapEditor::_text_changed(const String &p_text) { + _update_palette(); +} + +void RTileMapEditor::_sbox_input(const Ref &p_ie) { + Ref k = p_ie; + + if (k.is_valid() && (k->get_scancode() == KEY_UP || k->get_scancode() == KEY_DOWN || k->get_scancode() == KEY_PAGEUP || k->get_scancode() == KEY_PAGEDOWN)) { + palette->call("_gui_input", k); + search_box->accept_event(); + } +} + +// Implementation detail of RTileMapEditor::_update_palette(); +// In modern C++ this could have been inside its body. +namespace { +struct _PaletteEntry { + int id; + String name; + + bool operator<(const _PaletteEntry &p_rhs) const { + // Natural no case comparison will compare strings based on CharType + // order (except digits) and on numbers that start on the same position. + return name.naturalnocasecmp_to(p_rhs.name) < 0; + } +}; +} // namespace + +void RTileMapEditor::_update_palette() { + if (!node) { + return; + } + + // Update the clear button. + clear_transform_button->set_disabled(!flip_h && !flip_v && !transpose); + + // Update the palette. + Vector selected = get_selected_tiles(); + int selected_single = palette->get_current(); + int selected_manual = manual_palette->get_current(); + palette->clear(); + manual_palette->clear(); + manual_palette->hide(); + + Ref tileset = node->get_tileset(); + if (tileset.is_null()) { + search_box->set_text(""); + search_box->set_editable(false); + info_message->show(); + return; + } + + search_box->set_editable(true); + info_message->hide(); + + List tiles; + tileset->get_tile_list(&tiles); + if (tiles.empty()) { + return; + } + + float min_size = EDITOR_DEF("editors/tile_map/preview_size", 64); + min_size *= EDSCALE; + int hseparation = EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); + bool show_tile_names = bool(EDITOR_DEF("editors/tile_map/show_tile_names", true)); + bool show_tile_ids = bool(EDITOR_DEF("editors/tile_map/show_tile_ids", false)); + bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true)); + + palette->add_constant_override("hseparation", hseparation * EDSCALE); + + palette->set_fixed_icon_size(Size2(min_size, min_size)); + palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1)); + palette->set_same_column_width(true); + manual_palette->set_fixed_icon_size(Size2(min_size, min_size)); + manual_palette->set_same_column_width(true); + + String filter = search_box->get_text().strip_edges(); + + Vector<_PaletteEntry> entries; + + for (List::Element *E = tiles.front(); E; E = E->next()) { + String name = tileset->tile_get_name(E->get()); + + if (name != "") { + if (show_tile_ids) { + if (sort_by_name) { + name = name + " - " + itos(E->get()); + } else { + name = itos(E->get()) + " - " + name; + } + } + } else { + name = "#" + itos(E->get()); + } + + if (filter != "" && !filter.is_subsequence_ofi(name)) { + continue; + } + + const _PaletteEntry entry = { E->get(), name }; + entries.push_back(entry); + } + + if (sort_by_name) { + entries.sort(); + } + + for (int i = 0; i < entries.size(); i++) { + if (show_tile_names) { + palette->add_item(entries[i].name); + } else { + palette->add_item(String()); + } + + Ref tex = tileset->tile_get_texture(entries[i].id); + + if (tex.is_valid()) { + Rect2 region = tileset->tile_get_region(entries[i].id); + + if (tileset->tile_get_tile_mode(entries[i].id) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(entries[i].id) == RTileSet::ATLAS_TILE) { + int spacing = tileset->autotile_get_spacing(entries[i].id); + region.size = tileset->autotile_get_size(entries[i].id); + region.position += (region.size + Vector2(spacing, spacing)) * tileset->autotile_get_icon_coordinate(entries[i].id); + } + + // Transpose and flip. + palette->set_item_icon_transposed(palette->get_item_count() - 1, transpose); + if (flip_h) { + region.size.x = -region.size.x; + } + if (flip_v) { + region.size.y = -region.size.y; + } + + // Set region. + if (region.size != Size2()) { + palette->set_item_icon_region(palette->get_item_count() - 1, region); + } + + // Set icon. + palette->set_item_icon(palette->get_item_count() - 1, tex); + + // Modulation. + Color color = tileset->tile_get_modulate(entries[i].id); + palette->set_item_icon_modulate(palette->get_item_count() - 1, color); + } + + palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id); + } + + int sel_tile = selected.get(0); + if (selected.get(0) != RTileMap::INVALID_CELL) { + set_selected_tiles(selected); + sel_tile = selected.get(Math::rand() % selected.size()); + } else if (palette->get_item_count() > 0) { + palette->select(0); + sel_tile = palette->get_selected_items().get(0); + } + + if (sel_tile != RTileMap::INVALID_CELL && tileset->has_tile(sel_tile) && ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == RTileSet::AUTO_TILE) || (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == RTileSet::ATLAS_TILE))) { + const Map &tiles2 = tileset->autotile_get_bitmask_map(sel_tile); + + Vector entries2; + for (const Map::Element *E = tiles2.front(); E; E = E->next()) { + entries2.push_back(E->key()); + } + // Sort tiles in row-major order. + struct SwapComparator { + _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const { + return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x; + } + }; + entries2.sort_custom(); + + Ref tex = tileset->tile_get_texture(sel_tile); + Color modulate = tileset->tile_get_modulate(sel_tile); + + for (int i = 0; i < entries2.size(); i++) { + manual_palette->add_item(String()); + + if (tex.is_valid()) { + Rect2 region = tileset->tile_get_region(sel_tile); + int spacing = tileset->autotile_get_spacing(sel_tile); + region.size = tileset->autotile_get_size(sel_tile); // !! + region.position += (region.size + Vector2(spacing, spacing)) * entries2[i]; + + if (!region.has_no_area()) { + manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region); + } + + manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); + manual_palette->set_item_icon_modulate(manual_palette->get_item_count() - 1, modulate); + } + + manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]); + } + } + + if (manual_palette->get_item_count() > 0) { + // Only show the manual palette if at least tile exists in it. + if (selected_manual == -1 || selected_single != palette->get_current()) { + selected_manual = 0; + } + if (selected_manual < manual_palette->get_item_count()) { + manual_palette->set_current(selected_manual); + } + manual_palette->show(); + } + + if (sel_tile != RTileMap::INVALID_CELL && tileset->has_tile(sel_tile) && tileset->tile_get_tile_mode(sel_tile) == RTileSet::AUTO_TILE) { + manual_button->show(); + priority_button->hide(); + } else { + manual_button->hide(); + priority_button->show(); + } +} + +void RTileMapEditor::_pick_tile(const Point2 &p_pos) { + int id = node->get_cell(p_pos.x, p_pos.y); + + if (id == RTileMap::INVALID_CELL || !node->get_tileset()->has_tile(id)) { + return; + } + + if (search_box->get_text() != "") { + search_box->set_text(""); + _update_palette(); + } + + flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); + flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); + transpose = node->is_cell_transposed(p_pos.x, p_pos.y); + autotile_coord = node->get_cell_autotile_coord(p_pos.x, p_pos.y); + + Vector selected; + selected.push_back(id); + set_selected_tiles(selected); + _update_palette(); + + if ((manual_autotile && node->get_tileset()->tile_get_tile_mode(id) == RTileSet::AUTO_TILE) || (!priority_atlastile && node->get_tileset()->tile_get_tile_mode(id) == RTileSet::ATLAS_TILE)) { + manual_palette->select(manual_palette->find_metadata((Point2)autotile_coord)); + } + + CanvasItemEditor::get_singleton()->update_viewport(); +} + +PoolVector RTileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) { + int prev_id = node->get_cell(p_start.x, p_start.y); + Vector ids; + ids.push_back(RTileMap::INVALID_CELL); + if (!erase) { + ids = get_selected_tiles(); + + if (ids.size() == 0 || ids[0] == RTileMap::INVALID_CELL) { + return PoolVector(); + } + } else if (prev_id == RTileMap::INVALID_CELL) { + return PoolVector(); + } + + // Check if the tile variation is the same + if (ids.size() == 1 && ids[0] == prev_id) { + int current = manual_palette->get_current(); + if (current == -1) { + // Same ID, no variation selected, nothing to change + return PoolVector(); + } + Vector2 prev_autotile_coord = node->get_cell_autotile_coord(p_start.x, p_start.y); + Vector2 autotile_coord = manual_palette->get_item_metadata(current); + if (autotile_coord == prev_autotile_coord) { + // Same ID and variation, nothing to change + return PoolVector(); + } + } + + Rect2i r = node->get_used_rect(); + + int area = r.get_area(); + if (preview) { + // Test if we can re-use the result from preview bucket fill + bool invalidate_cache = false; + // Area changed + if (r != bucket_cache_rect) { + _clear_bucket_cache(); + } + // Cache grid is not initialized + if (bucket_cache_visited == nullptr) { + bucket_cache_visited = new bool[area]; + invalidate_cache = true; + } + // Tile ID changed or position wasn't visited by the previous fill + const int loc = (p_start.x - r.position.x) + (p_start.y - r.position.y) * r.get_size().x; + const bool in_range = 0 <= loc && loc < area; + if (prev_id != bucket_cache_tile || (in_range && !bucket_cache_visited[loc])) { + invalidate_cache = true; + } + if (invalidate_cache) { + for (int i = 0; i < area; ++i) { + bucket_cache_visited[i] = false; + } + bucket_cache = PoolVector(); + bucket_cache_tile = prev_id; + bucket_cache_rect = r; + bucket_queue.clear(); + } + } + + PoolVector points; + Vector non_preview_cache; + int count = 0; + int limit = 0; + + if (preview) { + limit = 1024; + } else { + bucket_queue.clear(); + } + + bucket_queue.push_back(p_start); + + while (bucket_queue.size()) { + Point2i n = bucket_queue.front()->get(); + bucket_queue.pop_front(); + + if (!r.has_point(n)) { + continue; + } + + if (node->get_cell(n.x, n.y) == prev_id) { + if (preview) { + int loc = (n.x - r.position.x) + (n.y - r.position.y) * r.get_size().x; + if (bucket_cache_visited[loc]) { + continue; + } + bucket_cache_visited[loc] = true; + bucket_cache.push_back(n); + } else { + if (non_preview_cache.find(n) >= 0) { + continue; + } + points.push_back(n); + non_preview_cache.push_back(n); + } + + bucket_queue.push_back(Point2i(n.x, n.y + 1)); + bucket_queue.push_back(Point2i(n.x, n.y - 1)); + bucket_queue.push_back(Point2i(n.x + 1, n.y)); + bucket_queue.push_back(Point2i(n.x - 1, n.y)); + count++; + } + + if (limit > 0 && count >= limit) { + break; + } + } + + return preview ? bucket_cache : points; +} + +void RTileMapEditor::_fill_points(const PoolVector &p_points, const Dictionary &p_op) { + int len = p_points.size(); + PoolVector::Read pr = p_points.read(); + + Vector ids = p_op["id"]; + bool xf = p_op["flip_h"]; + bool yf = p_op["flip_v"]; + bool tr = p_op["transpose"]; + + for (int i = 0; i < len; i++) { + _set_cell(pr[i], ids, xf, yf, tr); + node->make_bitmask_area_dirty(pr[i]); + } + if (!manual_autotile) { + node->update_dirty_bitmask(); + } +} + +void RTileMapEditor::_erase_points(const PoolVector &p_points) { + int len = p_points.size(); + PoolVector::Read pr = p_points.read(); + + for (int i = 0; i < len; i++) { + _set_cell(pr[i], invalid_cell); + } +} + +void RTileMapEditor::_select(const Point2i &p_from, const Point2i &p_to) { + Point2i begin = p_from; + Point2i end = p_to; + + if (begin.x > end.x) { + SWAP(begin.x, end.x); + } + if (begin.y > end.y) { + SWAP(begin.y, end.y); + } + + rectangle.position = begin; + rectangle.size = end - begin; + + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void RTileMapEditor::_erase_selection() { + if (!selection_active) { + return; + } + + for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { + for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { + _set_cell(Point2i(j, i), invalid_cell, false, false, false); + } + } +} + +void RTileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) { + if (!node->get_tileset()->has_tile(p_cell)) { + return; + } + + Ref t = node->get_tileset()->tile_get_texture(p_cell); + if (t.is_null()) { + return; + } + + Vector2 tile_ofs = node->get_tileset()->tile_get_texture_offset(p_cell); + + Rect2 r = node->get_tileset()->tile_get_region(p_cell); + if (node->get_tileset()->tile_get_tile_mode(p_cell) == RTileSet::AUTO_TILE || node->get_tileset()->tile_get_tile_mode(p_cell) == RTileSet::ATLAS_TILE) { + Vector2 offset; + if (tool != TOOL_PASTING) { + int selected = manual_palette->get_current(); + if ((manual_autotile || (node->get_tileset()->tile_get_tile_mode(p_cell) == RTileSet::ATLAS_TILE && !priority_atlastile)) && selected != -1) { + offset = manual_palette->get_item_metadata(selected); + } else { + offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell); + } + } else { + offset = p_autotile_coord; + } + + int spacing = node->get_tileset()->autotile_get_spacing(p_cell); + r.size = node->get_tileset()->autotile_get_size(p_cell); + r.position += (r.size + Vector2(spacing, spacing)) * offset; + } + Size2 cell_size = node->get_cell_size(); + bool centered_texture = node->is_centered_textures_enabled(); + bool compatibility_mode_enabled = node->is_compatibility_mode_enabled(); + Rect2 rect = Rect2(); + rect.position = node->map_to_world(p_point) + node->get_cell_draw_offset(); + + if (r.has_no_area()) { + rect.size = t->get_size(); + } else { + rect.size = r.size; + } + + if (compatibility_mode_enabled && !centered_texture) { + if (rect.size.y > rect.size.x) { + if ((p_flip_h && (p_flip_v || p_transpose)) || (p_flip_v && !p_transpose)) { + tile_ofs.y += rect.size.y - rect.size.x; + } + } else if (rect.size.y < rect.size.x) { + if ((p_flip_v && (p_flip_h || p_transpose)) || (p_flip_h && !p_transpose)) { + tile_ofs.x += rect.size.x - rect.size.y; + } + } + } + + if (p_transpose) { + SWAP(tile_ofs.x, tile_ofs.y); + if (centered_texture) { + rect.position.x += cell_size.x / 2 - rect.size.y / 2; + rect.position.y += cell_size.y / 2 - rect.size.x / 2; + } + } else if (centered_texture) { + rect.position += cell_size / 2 - rect.size / 2; + } + + if (p_flip_h) { + rect.size.x *= -1.0; + tile_ofs.x *= -1.0; + } + + if (p_flip_v) { + rect.size.y *= -1.0; + tile_ofs.y *= -1.0; + } + + if (compatibility_mode_enabled && !centered_texture) { + if (node->get_tile_origin() == RTileMap::TILE_ORIGIN_TOP_LEFT) { + rect.position += tile_ofs; + } else if (node->get_tile_origin() == RTileMap::TILE_ORIGIN_BOTTOM_LEFT) { + rect.position += tile_ofs; + + if (p_transpose) { + if (p_flip_h) { + rect.position.x -= cell_size.x; + } else { + rect.position.x += cell_size.x; + } + } else { + if (p_flip_v) { + rect.position.y -= cell_size.y; + } else { + rect.position.y += cell_size.y; + } + } + + } else if (node->get_tile_origin() == RTileMap::TILE_ORIGIN_CENTER) { + rect.position += tile_ofs; + + if (p_flip_h) { + rect.position.x -= cell_size.x / 2; + } else { + rect.position.x += cell_size.x / 2; + } + + if (p_flip_v) { + rect.position.y -= cell_size.y / 2; + } else { + rect.position.y += cell_size.y / 2; + } + } + } else { + rect.position += tile_ofs; + } + + Color modulate = node->get_tileset()->tile_get_modulate(p_cell); + modulate.a = 0.5; + + Transform2D old_transform = p_viewport->get_viewport_transform(); + p_viewport->draw_set_transform_matrix(p_xform); // Take into account TileMap transformation when displaying cell + if (r.has_no_area()) { + p_viewport->draw_texture_rect(t, rect, false, modulate, p_transpose); + } else { + p_viewport->draw_texture_rect_region(t, rect, r, modulate, p_transpose); + } + p_viewport->draw_set_transform_matrix(old_transform); +} + +void RTileMapEditor::_draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) { + PoolVector points = _bucket_fill(p_point, false, true); + PoolVector::Read pr = points.read(); + int len = points.size(); + + for (int i = 0; i < len; ++i) { + _draw_cell(p_viewport, p_cell, pr[i], p_flip_h, p_flip_v, p_transpose, p_autotile_coord, p_xform); + } +} + +void RTileMapEditor::_clear_bucket_cache() { + if (bucket_cache_visited) { + delete[] bucket_cache_visited; + bucket_cache_visited = nullptr; + } +} + +void RTileMapEditor::_update_copydata() { + copydata.clear(); + + if (!selection_active) { + return; + } + + for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { + for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { + TileData tcd; + + tcd.cell = node->get_cell(j, i); + if (tcd.cell != RTileMap::INVALID_CELL) { + tcd.pos = Point2i(j, i); + tcd.flip_h = node->is_cell_x_flipped(j, i); + tcd.flip_v = node->is_cell_y_flipped(j, i); + tcd.transpose = node->is_cell_transposed(j, i); + tcd.autotile_coord = node->get_cell_autotile_coord(j, i); + + copydata.push_back(tcd); + } + } + } +} + +static inline Vector line(int x0, int x1, int y0, int y1) { + Vector points; + + float dx = ABS(x1 - x0); + float dy = ABS(y1 - y0); + + int x = x0; + int y = y0; + + int sx = x0 > x1 ? -1 : 1; + int sy = y0 > y1 ? -1 : 1; + + if (dx > dy) { + float err = dx / 2; + + for (; x != x1; x += sx) { + points.push_back(Vector2(x, y)); + + err -= dy; + if (err < 0) { + y += sy; + err += dx; + } + } + } else { + float err = dy / 2; + + for (; y != y1; y += sy) { + points.push_back(Vector2(x, y)); + + err -= dx; + if (err < 0) { + x += sx; + err += dy; + } + } + } + + points.push_back(Vector2(x, y)); + + return points; +} + +bool RTileMapEditor::forward_gui_input(const Ref &p_event) { + if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree() || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { + return false; + } + + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform(); + Transform2D xform_inv = xform.affine_inverse(); + + Ref mb = p_event; + + if (mb.is_valid()) { + if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->is_pressed()) { + if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + return false; // Drag. + } + + if (tool == TOOL_NONE) { + if (mb->get_shift()) { + if (mb->get_command()) { + tool = TOOL_RECTANGLE_PAINT; + } else { + tool = TOOL_LINE_PAINT; + } + + selection_active = false; + rectangle_begin = over_tile; + + _update_button_tool(); + return true; + } + + if (mb->get_command()) { + tool = TOOL_PICKING; + _pick_tile(over_tile); + _update_button_tool(); + + return true; + } + + tool = TOOL_PAINTING; + _update_button_tool(); + } + + if (tool == TOOL_PAINTING) { + Vector ids = get_selected_tiles(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + tool = TOOL_PAINTING; + + _start_undo(TTR("Paint TileMap")); + } + } else if (tool == TOOL_PICKING) { + _pick_tile(over_tile); + } else if (tool == TOOL_SELECTING) { + selection_active = true; + rectangle_begin = over_tile; + } + + _update_button_tool(); + return true; + + } else { + // Mousebutton was released. + if (tool != TOOL_NONE) { + if (tool == TOOL_PAINTING) { + Vector ids = get_selected_tiles(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + _set_cell(over_tile, ids, flip_h, flip_v, transpose); + _finish_undo(); + + paint_undo.clear(); + } + } else if (tool == TOOL_LINE_PAINT) { + Vector ids = get_selected_tiles(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + _start_undo(TTR("Line Draw")); + for (Map::Element *E = paint_undo.front(); E; E = E->next()) { + _set_cell(E->key(), ids, flip_h, flip_v, transpose); + } + _finish_undo(); + + paint_undo.clear(); + + CanvasItemEditor::get_singleton()->update_viewport(); + } + } else if (tool == TOOL_RECTANGLE_PAINT) { + Vector ids = get_selected_tiles(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + _start_undo(TTR("Rectangle Paint")); + for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { + for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { + _set_cell(Point2i(j, i), ids, flip_h, flip_v, transpose); + } + } + _finish_undo(); + + CanvasItemEditor::get_singleton()->update_viewport(); + } + } else if (tool == TOOL_PASTING) { + Point2 ofs = over_tile - rectangle.position; + Vector ids; + + _start_undo(TTR("Paste")); + ids.push_back(0); + for (List::Element *E = copydata.front(); E; E = E->next()) { + ids.write[0] = E->get().cell; + _set_cell(E->get().pos + ofs, ids, E->get().flip_h, E->get().flip_v, E->get().transpose, E->get().autotile_coord); + } + _finish_undo(); + + CanvasItemEditor::get_singleton()->update_viewport(); + + return true; // We want to keep the Pasting tool. + } else if (tool == TOOL_SELECTING) { + CanvasItemEditor::get_singleton()->update_viewport(); + + } else if (tool == TOOL_BUCKET) { + PoolVector points = _bucket_fill(over_tile); + + if (points.size() == 0) { + return false; + } + + _start_undo(TTR("Bucket Fill")); + + Dictionary op; + op["id"] = get_selected_tiles(); + op["flip_h"] = flip_h; + op["flip_v"] = flip_v; + op["transpose"] = transpose; + + _fill_points(points, op); + + _finish_undo(); + + // So the fill preview is cleared right after the click. + CanvasItemEditor::get_singleton()->update_viewport(); + + // We want to keep the bucket-tool active. + return true; + } + + tool = TOOL_NONE; + _update_button_tool(); + + return true; + } + } + } else if (mb->get_button_index() == BUTTON_RIGHT) { + if (mb->is_pressed()) { + if (tool == TOOL_SELECTING || selection_active) { + tool = TOOL_NONE; + selection_active = false; + + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + + if (tool == TOOL_PASTING) { + tool = TOOL_NONE; + copydata.clear(); + + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + + if (tool == TOOL_NONE) { + paint_undo.clear(); + + Point2 local = node->world_to_map(xform_inv.xform(mb->get_position())); + + _start_undo(TTR("Erase TileMap")); + + if (mb->get_shift()) { + if (mb->get_command()) { + tool = TOOL_RECTANGLE_ERASE; + } else { + tool = TOOL_LINE_ERASE; + } + + selection_active = false; + rectangle_begin = local; + } else { + tool = TOOL_ERASING; + + _set_cell(local, invalid_cell); + } + + _update_button_tool(); + return true; + } + + } else { + if (tool == TOOL_ERASING || tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) { + _finish_undo(); + + if (tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) { + CanvasItemEditor::get_singleton()->update_viewport(); + } + + tool = TOOL_NONE; + + _update_button_tool(); + return true; + + } else if (tool == TOOL_BUCKET) { + Vector ids; + ids.push_back(node->get_cell(over_tile.x, over_tile.y)); + Dictionary pop; + pop["id"] = ids; + pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y); + pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y); + pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y); + + PoolVector points = _bucket_fill(over_tile, true); + + if (points.size() == 0) { + return false; + } + + undo_redo->create_action(TTR("Bucket Fill")); + + undo_redo->add_do_method(this, "_erase_points", points); + undo_redo->add_undo_method(this, "_fill_points", points, pop); + + undo_redo->commit_action(); + } + } + } + } + + Ref mm = p_event; + + if (mm.is_valid()) { + Point2i new_over_tile = node->world_to_map(xform_inv.xform(mm->get_position())); + Point2i old_over_tile = over_tile; + + if (new_over_tile != over_tile) { + over_tile = new_over_tile; + CanvasItemEditor::get_singleton()->update_viewport(); + } + + if (refocus_over_tile) { + // editor lost focus; forget last tile position + old_over_tile = new_over_tile; + refocus_over_tile = false; + } + + int tile_under = node->get_cell(over_tile.x, over_tile.y); + String tile_name = "none"; + + if (node->get_tileset()->has_tile(tile_under)) { + tile_name = node->get_tileset()->tile_get_name(tile_under); + } + tile_info->show(); + tile_info->set_text(String::num(over_tile.x) + ", " + String::num(over_tile.y) + " [" + tile_name + "]"); + + if (tool == TOOL_PAINTING) { + // Paint using bresenham line to prevent holes in painting if the user moves fast. + + Vector points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); + Vector ids = get_selected_tiles(); + + for (int i = 0; i < points.size(); ++i) { + Point2i pos = points[i]; + + if (!paint_undo.has(pos)) { + paint_undo[pos] = _get_op_from_cell(pos); + } + + _set_cell(pos, ids, flip_h, flip_v, transpose); + } + + return true; + } + + if (tool == TOOL_ERASING) { + // Erase using bresenham line to prevent holes in painting if the user moves fast. + + Vector points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); + + for (int i = 0; i < points.size(); ++i) { + Point2i pos = points[i]; + + _set_cell(pos, invalid_cell); + } + + return true; + } + + if (tool == TOOL_SELECTING) { + _select(rectangle_begin, over_tile); + + return true; + } + + if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) { + Vector ids = get_selected_tiles(); + Vector tmp_cell; + bool erasing = (tool == TOOL_LINE_ERASE); + + tmp_cell.push_back(0); + if (erasing && paint_undo.size()) { + for (Map::Element *E = paint_undo.front(); E; E = E->next()) { + tmp_cell.write[0] = E->get().idx; + _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); + } + } + + paint_undo.clear(); + + if (ids.size() > 0 && ids[0] != RTileMap::INVALID_CELL) { + Vector points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y); + + for (int i = 0; i < points.size(); i++) { + paint_undo[points[i]] = _get_op_from_cell(points[i]); + + if (erasing) { + _set_cell(points[i], invalid_cell); + } + } + + CanvasItemEditor::get_singleton()->update_viewport(); + } + + return true; + } + if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) { + Vector tmp_cell; + tmp_cell.push_back(0); + + _select(rectangle_begin, over_tile); + + if (tool == TOOL_RECTANGLE_ERASE) { + if (paint_undo.size()) { + for (Map::Element *E = paint_undo.front(); E; E = E->next()) { + tmp_cell.write[0] = E->get().idx; + _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); + } + } + + paint_undo.clear(); + + for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { + for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { + Point2i tile = Point2i(j, i); + paint_undo[tile] = _get_op_from_cell(tile); + + _set_cell(tile, invalid_cell); + } + } + } + + return true; + } + if (tool == TOOL_PICKING && Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) { + _pick_tile(over_tile); + + return true; + } + } + + Ref k = p_event; + + if (k.is_valid() && k->is_pressed()) { + if (last_tool == TOOL_NONE && tool == TOOL_PICKING && k->get_scancode() == KEY_SHIFT && k->get_command()) { + // trying to draw a rectangle with the painting tool, so change to the correct tool + tool = last_tool; + + CanvasItemEditor::get_singleton()->update_viewport(); + _update_button_tool(); + } + + if (k->get_scancode() == KEY_ESCAPE) { + if (tool == TOOL_PASTING) { + copydata.clear(); + } else if (tool == TOOL_SELECTING || selection_active) { + selection_active = false; + } + + tool = TOOL_NONE; + + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + + if (!mouse_over) { + // Editor shortcuts should not fire if mouse not in viewport. + return false; + } + + if (ED_IS_SHORTCUT("tile_map_editor/paint_tile", p_event)) { + // NOTE: We do not set tool = TOOL_PAINTING as this begins painting + // immediately without pressing the left mouse button first. + tool = TOOL_NONE; + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/bucket_fill", p_event)) { + tool = TOOL_BUCKET; + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/erase_selection", p_event)) { + _menu_option(OPTION_ERASE_SELECTION); + + _update_button_tool(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/select", p_event)) { + tool = TOOL_SELECTING; + selection_active = false; + + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/copy_selection", p_event)) { + _update_copydata(); + + if (selection_active) { + tool = TOOL_PASTING; + + CanvasItemEditor::get_singleton()->update_viewport(); + + _update_button_tool(); + return true; + } + } + if (ED_IS_SHORTCUT("tile_map_editor/cut_selection", p_event)) { + if (selection_active) { + _update_copydata(); + + _start_undo(TTR("Cut Selection")); + _erase_selection(); + _finish_undo(); + + selection_active = false; + + tool = TOOL_PASTING; + + CanvasItemEditor::get_singleton()->update_viewport(); + _update_button_tool(); + return true; + } + } + if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) { + search_box->select_all(); + search_box->grab_focus(); + + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/rotate_left", p_event)) { + _rotate(-1); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/rotate_right", p_event)) { + _rotate(1); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/flip_horizontal", p_event)) { + _flip_horizontal(); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/flip_vertical", p_event)) { + _flip_vertical(); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/clear_transform", p_event)) { + _clear_transform(); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) { + transpose = !transpose; + _update_palette(); + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + } else if (k.is_valid()) { // Release event. + + if (tool == TOOL_NONE) { + if (k->get_scancode() == KEY_SHIFT && k->get_command()) { + tool = TOOL_PICKING; + _update_button_tool(); + } + } else if (tool == TOOL_PICKING) { +#ifdef APPLE_STYLE_KEYS + if (k->get_scancode() == KEY_META) { +#else + if (k->get_scancode() == KEY_CONTROL) { +#endif + // Go back to that last tool if KEY_CONTROL was released. + tool = last_tool; + + CanvasItemEditor::get_singleton()->update_viewport(); + _update_button_tool(); + } + } + } + return false; +} + +void RTileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { + return; + } + + Transform2D cell_xf = node->get_cell_transform(); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform(); + Transform2D xform_inv = xform.affine_inverse(); + + Size2 screen_size = p_overlay->get_size(); + { + Rect2 aabb; + aabb.position = node->world_to_map(xform_inv.xform(Vector2())); + aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height)))); + aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0)))); + aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size))); + Rect2i si = aabb.grow(1.0); + + if (node->get_half_offset() != RTileMap::HALF_OFFSET_X && node->get_half_offset() != RTileMap::HALF_OFFSET_NEGATIVE_X) { + int max_lines = 2000; //avoid crash if size too small + + for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) { + Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.position.y))); + Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.position.y + si.size.y + 1))); + + Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); + p_overlay->draw_line(from, to, col, 1); + if (max_lines-- == 0) { + break; + } + } + } else { + int max_lines = 10000; //avoid crash if size too small + + for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) { + for (int j = (si.position.y) - 1; j <= (si.position.y + si.size.y); j++) { + Vector2 ofs; + if (ABS(j) & 1) { + ofs = cell_xf[0] * (node->get_half_offset() == RTileMap::HALF_OFFSET_X ? 0.5 : -0.5); + } + + Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs); + Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs); + + Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); + p_overlay->draw_line(from, to, col, 1); + + if (--max_lines == 0) { + break; + } + } + if (max_lines == 0) { + break; + } + } + } + + int max_lines = 10000; //avoid crash if size too small + + if (node->get_half_offset() != RTileMap::HALF_OFFSET_Y && node->get_half_offset() != RTileMap::HALF_OFFSET_NEGATIVE_Y) { + for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) { + Vector2 from = xform.xform(node->map_to_world(Vector2(si.position.x, i))); + Vector2 to = xform.xform(node->map_to_world(Vector2(si.position.x + si.size.x + 1, i))); + + Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); + p_overlay->draw_line(from, to, col, 1); + + if (max_lines-- == 0) { + break; + } + } + } else { + for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) { + for (int j = (si.position.x) - 1; j <= (si.position.x + si.size.x); j++) { + Vector2 ofs; + if (ABS(j) & 1) { + ofs = cell_xf[1] * (node->get_half_offset() == RTileMap::HALF_OFFSET_Y ? 0.5 : -0.5); + } + + Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs); + Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs); + + Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); + p_overlay->draw_line(from, to, col, 1); + + if (--max_lines == 0) { + break; + } + } + if (max_lines == 0) { + break; + } + } + } + } + + if (selection_active) { + Vector points; + points.push_back(xform.xform(node->map_to_world((rectangle.position)))); + points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, 0))))); + points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, rectangle.size.y + 1))))); + points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(0, rectangle.size.y + 1))))); + + p_overlay->draw_colored_polygon(points, Color(0.2, 0.8, 1, 0.4)); + } + + if (mouse_over && node->get_tileset().is_valid()) { + Vector2 endpoints[4] = { + node->map_to_world(over_tile, true), + node->map_to_world((over_tile + Point2(1, 0)), true), + node->map_to_world((over_tile + Point2(1, 1)), true), + node->map_to_world((over_tile + Point2(0, 1)), true) + }; + + for (int i = 0; i < 4; i++) { + if (node->get_half_offset() == RTileMap::HALF_OFFSET_X && ABS(over_tile.y) & 1) { + endpoints[i] += cell_xf[0] * 0.5; + } + if (node->get_half_offset() == RTileMap::HALF_OFFSET_NEGATIVE_X && ABS(over_tile.y) & 1) { + endpoints[i] += cell_xf[0] * -0.5; + } + if (node->get_half_offset() == RTileMap::HALF_OFFSET_Y && ABS(over_tile.x) & 1) { + endpoints[i] += cell_xf[1] * 0.5; + } + if (node->get_half_offset() == RTileMap::HALF_OFFSET_NEGATIVE_Y && ABS(over_tile.x) & 1) { + endpoints[i] += cell_xf[1] * -0.5; + } + endpoints[i] = xform.xform(endpoints[i]); + } + Color col; + if (node->get_cell(over_tile.x, over_tile.y) != RTileMap::INVALID_CELL) { + col = Color(0.2, 0.8, 1.0, 0.8); + } else { + col = Color(1.0, 0.4, 0.2, 0.8); + } + + for (int i = 0; i < 4; i++) { + p_overlay->draw_line(endpoints[i], endpoints[(i + 1) % 4], col, 2); + } + + bool bucket_preview = EditorSettings::get_singleton()->get("editors/tile_map/bucket_fill_preview"); + if (tool == TOOL_SELECTING || tool == TOOL_PICKING || !bucket_preview) { + return; + } + + if (tool == TOOL_LINE_PAINT) { + if (paint_undo.empty()) { + return; + } + + Vector ids = get_selected_tiles(); + + if (ids.size() == 1 && ids[0] == RTileMap::INVALID_CELL) { + return; + } + + for (Map::Element *E = paint_undo.front(); E; E = E->next()) { + _draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform); + } + + } else if (tool == TOOL_RECTANGLE_PAINT) { + Vector ids = get_selected_tiles(); + + if (ids.size() == 1 && ids[0] == RTileMap::INVALID_CELL) { + return; + } + + for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { + for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { + _draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform); + } + } + } else if (tool == TOOL_PASTING) { + if (copydata.empty()) { + return; + } + + Ref ts = node->get_tileset(); + + if (ts.is_null()) { + return; + } + + Point2 ofs = over_tile - rectangle.position; + + for (List::Element *E = copydata.front(); E; E = E->next()) { + if (!ts->has_tile(E->get().cell)) { + continue; + } + + TileData tcd = E->get(); + + _draw_cell(p_overlay, tcd.cell, tcd.pos + ofs, tcd.flip_h, tcd.flip_v, tcd.transpose, tcd.autotile_coord, xform); + } + + Rect2i duplicate = rectangle; + duplicate.position = over_tile; + + Vector points; + points.push_back(xform.xform(node->map_to_world(duplicate.position))); + points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, 0))))); + points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, duplicate.size.y + 1))))); + points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(0, duplicate.size.y + 1))))); + + p_overlay->draw_colored_polygon(points, Color(0.2, 1.0, 0.8, 0.2)); + + } else if (tool == TOOL_BUCKET) { + Vector tiles = get_selected_tiles(); + _draw_fill_preview(p_overlay, tiles[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform); + + } else { + Vector st = get_selected_tiles(); + + if (st.size() == 1 && st[0] == RTileMap::INVALID_CELL) { + return; + } + + _draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform); + } + } +} + +void RTileMapEditor::edit(Node *p_tile_map) { + search_box->set_text(""); + + if (!canvas_item_editor_viewport) { + canvas_item_editor_viewport = CanvasItemEditor::get_singleton()->get_viewport_control(); + } + + if (node && node->is_connected("settings_changed", this, "_tileset_settings_changed")) { + node->disconnect("settings_changed", this, "_tileset_settings_changed"); + } + + if (p_tile_map) { + node = Object::cast_to(p_tile_map); + if (!canvas_item_editor_viewport->is_connected("mouse_entered", this, "_canvas_mouse_enter")) { + canvas_item_editor_viewport->connect("mouse_entered", this, "_canvas_mouse_enter"); + } + if (!canvas_item_editor_viewport->is_connected("mouse_exited", this, "_canvas_mouse_exit")) { + canvas_item_editor_viewport->connect("mouse_exited", this, "_canvas_mouse_exit"); + } + + _update_palette(); + + } else { + node = nullptr; + + if (canvas_item_editor_viewport->is_connected("mouse_entered", this, "_canvas_mouse_enter")) { + canvas_item_editor_viewport->disconnect("mouse_entered", this, "_canvas_mouse_enter"); + } + if (canvas_item_editor_viewport->is_connected("mouse_exited", this, "_canvas_mouse_exit")) { + canvas_item_editor_viewport->disconnect("mouse_exited", this, "_canvas_mouse_exit"); + } + + _update_palette(); + } + + if (node && !node->is_connected("settings_changed", this, "_tileset_settings_changed")) { + node->connect("settings_changed", this, "_tileset_settings_changed"); + } + + _clear_bucket_cache(); +} + +void RTileMapEditor::_tileset_settings_changed() { + _update_palette(); + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void RTileMapEditor::_icon_size_changed(float p_value) { + if (node) { + palette->set_icon_scale(p_value); + manual_palette->set_icon_scale(p_value); + _update_palette(); + } +} + +void RTileMapEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_manual_toggled"), &RTileMapEditor::_manual_toggled); + ClassDB::bind_method(D_METHOD("_priority_toggled"), &RTileMapEditor::_priority_toggled); + ClassDB::bind_method(D_METHOD("_text_entered"), &RTileMapEditor::_text_entered); + ClassDB::bind_method(D_METHOD("_text_changed"), &RTileMapEditor::_text_changed); + ClassDB::bind_method(D_METHOD("_sbox_input"), &RTileMapEditor::_sbox_input); + ClassDB::bind_method(D_METHOD("_button_tool_select"), &RTileMapEditor::_button_tool_select); + ClassDB::bind_method(D_METHOD("_menu_option"), &RTileMapEditor::_menu_option); + ClassDB::bind_method(D_METHOD("_canvas_mouse_enter"), &RTileMapEditor::_canvas_mouse_enter); + ClassDB::bind_method(D_METHOD("_canvas_mouse_exit"), &RTileMapEditor::_canvas_mouse_exit); + ClassDB::bind_method(D_METHOD("_tileset_settings_changed"), &RTileMapEditor::_tileset_settings_changed); + ClassDB::bind_method(D_METHOD("_rotate"), &RTileMapEditor::_rotate); + ClassDB::bind_method(D_METHOD("_flip_horizontal"), &RTileMapEditor::_flip_horizontal); + ClassDB::bind_method(D_METHOD("_flip_vertical"), &RTileMapEditor::_flip_vertical); + ClassDB::bind_method(D_METHOD("_clear_transform"), &RTileMapEditor::_clear_transform); + ClassDB::bind_method(D_METHOD("_palette_selected"), &RTileMapEditor::_palette_selected); + ClassDB::bind_method(D_METHOD("_palette_multi_selected"), &RTileMapEditor::_palette_multi_selected); + ClassDB::bind_method(D_METHOD("_palette_input"), &RTileMapEditor::_palette_input); + + ClassDB::bind_method(D_METHOD("_fill_points"), &RTileMapEditor::_fill_points); + ClassDB::bind_method(D_METHOD("_erase_points"), &RTileMapEditor::_erase_points); + + ClassDB::bind_method(D_METHOD("_icon_size_changed"), &RTileMapEditor::_icon_size_changed); + ClassDB::bind_method(D_METHOD("_node_removed"), &RTileMapEditor::_node_removed); +} + +RTileMapEditor::CellOp RTileMapEditor::_get_op_from_cell(const Point2i &p_pos) { + CellOp op; + op.idx = node->get_cell(p_pos.x, p_pos.y); + if (op.idx != RTileMap::INVALID_CELL) { + if (node->is_cell_x_flipped(p_pos.x, p_pos.y)) { + op.xf = true; + } + if (node->is_cell_y_flipped(p_pos.x, p_pos.y)) { + op.yf = true; + } + if (node->is_cell_transposed(p_pos.x, p_pos.y)) { + op.tr = true; + } + op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y); + } + return op; +} + +void RTileMapEditor::_rotate(int steps) { + const bool normal_rotation_matrix[][3] = { + { false, false, false }, + { true, true, false }, + { false, true, true }, + { true, false, true } + }; + + const bool mirrored_rotation_matrix[][3] = { + { false, true, false }, + { true, true, true }, + { false, false, true }, + { true, false, false } + }; + + if (transpose ^ flip_h ^ flip_v) { + // Odd number of flags activated = mirrored rotation + for (int i = 0; i < 4; i++) { + if (transpose == mirrored_rotation_matrix[i][0] && + flip_h == mirrored_rotation_matrix[i][1] && + flip_v == mirrored_rotation_matrix[i][2]) { + int new_id = Math::wrapi(i + steps, 0, 4); + transpose = mirrored_rotation_matrix[new_id][0]; + flip_h = mirrored_rotation_matrix[new_id][1]; + flip_v = mirrored_rotation_matrix[new_id][2]; + break; + } + } + } else { + // Even number of flags activated = normal rotation + for (int i = 0; i < 4; i++) { + if (transpose == normal_rotation_matrix[i][0] && + flip_h == normal_rotation_matrix[i][1] && + flip_v == normal_rotation_matrix[i][2]) { + int new_id = Math::wrapi(i + steps, 0, 4); + transpose = normal_rotation_matrix[new_id][0]; + flip_h = normal_rotation_matrix[new_id][1]; + flip_v = normal_rotation_matrix[new_id][2]; + break; + } + } + } + + _update_palette(); +} + +void RTileMapEditor::_flip_horizontal() { + flip_h = !flip_h; + _update_palette(); +} + +void RTileMapEditor::_flip_vertical() { + flip_v = !flip_v; + _update_palette(); +} + +void RTileMapEditor::_clear_transform() { + transpose = false; + flip_h = false; + flip_v = false; + _update_palette(); +} + +RTileMapEditor::RTileMapEditor(EditorNode *p_editor) { + node = nullptr; + manual_autotile = false; + priority_atlastile = false; + manual_position = Vector2(0, 0); + canvas_item_editor_viewport = nullptr; + editor = p_editor; + undo_redo = EditorNode::get_undo_redo(); + + tool = TOOL_NONE; + selection_active = false; + mouse_over = false; + + flip_h = false; + flip_v = false; + transpose = false; + + bucket_cache_tile = -1; + bucket_cache_visited = nullptr; + + invalid_cell.resize(1); + invalid_cell.write[0] = RTileMap::INVALID_CELL; + + ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase Selection"), KEY_DELETE); + ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find Tile"), KEY_MASK_CMD + KEY_F); + ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"), KEY_T); + + HBoxContainer *tool_hb = memnew(HBoxContainer); + add_child(tool_hb); + + manual_button = memnew(CheckBox); + manual_button->set_text(TTR("Disable Autotile")); + manual_button->connect("toggled", this, "_manual_toggled"); + add_child(manual_button); + + priority_button = memnew(CheckBox); + priority_button->set_text(TTR("Enable Priority")); + priority_button->connect("toggled", this, "_priority_toggled"); + add_child(priority_button); + + search_box = memnew(LineEdit); + search_box->set_placeholder(TTR("Filter tiles")); + search_box->set_h_size_flags(SIZE_EXPAND_FILL); + search_box->connect("text_entered", this, "_text_entered"); + search_box->connect("text_changed", this, "_text_changed"); + search_box->connect("gui_input", this, "_sbox_input"); + add_child(search_box); + + size_slider = memnew(HSlider); + size_slider->set_h_size_flags(SIZE_EXPAND_FILL); + size_slider->set_min(0.1f); + size_slider->set_max(4.0f); + size_slider->set_step(0.1f); + size_slider->set_value(1.0f); + size_slider->connect("value_changed", this, "_icon_size_changed"); + add_child(size_slider); + + int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80); + + VSplitContainer *palette_container = memnew(VSplitContainer); + palette_container->set_v_size_flags(SIZE_EXPAND_FILL); + palette_container->set_custom_minimum_size(Size2(mw, 0)); + add_child(palette_container); + + // Add tile palette. + palette = memnew(ItemList); + palette->set_h_size_flags(SIZE_EXPAND_FILL); + palette->set_v_size_flags(SIZE_EXPAND_FILL); + palette->set_max_columns(0); + palette->set_icon_mode(ItemList::ICON_MODE_TOP); + palette->set_max_text_lines(2); + palette->set_select_mode(ItemList::SELECT_MULTI); + palette->add_constant_override("vseparation", 8 * EDSCALE); + palette->connect("item_selected", this, "_palette_selected"); + palette->connect("multi_selected", this, "_palette_multi_selected"); + palette->connect("gui_input", this, "_palette_input"); + palette_container->add_child(palette); + + // Add message for when no texture is selected. + info_message = memnew(Label); + info_message->set_text(TTR("Give a TileSet resource to this TileMap to use its tiles.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + palette->add_child(info_message); + + // Add autotile override palette. + manual_palette = memnew(ItemList); + manual_palette->set_h_size_flags(SIZE_EXPAND_FILL); + manual_palette->set_v_size_flags(SIZE_EXPAND_FILL); + manual_palette->set_max_columns(0); + manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP); + manual_palette->set_max_text_lines(2); + manual_palette->hide(); + palette_container->add_child(manual_palette); + + // Add menu items. + toolbar = memnew(HBoxContainer); + toolbar->hide(); + CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar); + + toolbar->add_child(memnew(VSeparator)); + + // Tools. + paint_button = memnew(ToolButton); + paint_button->set_shortcut(ED_SHORTCUT("tile_map_editor/paint_tile", TTR("Paint Tile"), KEY_P)); +#ifdef OSX_ENABLED + paint_button->set_tooltip(TTR("Shift+LMB: Line Draw\nShift+Command+LMB: Rectangle Paint")); +#else + paint_button->set_tooltip(TTR("Shift+LMB: Line Draw\nShift+Ctrl+LMB: Rectangle Paint")); +#endif + paint_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_NONE)); + paint_button->set_toggle_mode(true); + toolbar->add_child(paint_button); + + bucket_fill_button = memnew(ToolButton); + bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_B)); + bucket_fill_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_BUCKET)); + bucket_fill_button->set_toggle_mode(true); + toolbar->add_child(bucket_fill_button); + + picker_button = memnew(ToolButton); + picker_button->set_shortcut(ED_SHORTCUT("tile_map_editor/pick_tile", TTR("Pick Tile"), KEY_I)); + picker_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_PICKING)); + picker_button->set_toggle_mode(true); + toolbar->add_child(picker_button); + + select_button = memnew(ToolButton); + select_button->set_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_M)); + select_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_SELECTING)); + select_button->set_toggle_mode(true); + toolbar->add_child(select_button); + + _update_button_tool(); + + // Container to the right of the toolbar. + toolbar_right = memnew(HBoxContainer); + toolbar_right->hide(); + toolbar_right->set_h_size_flags(SIZE_EXPAND_FILL); + toolbar_right->set_alignment(BoxContainer::ALIGN_END); + CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar_right); + + // Tile position. + tile_info = memnew(Label); + tile_info->set_modulate(Color(1, 1, 1, 0.8)); + tile_info->set_mouse_filter(MOUSE_FILTER_IGNORE); + tile_info->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("main", "EditorFonts")); + // The tile info is only displayed after a tile has been hovered. + tile_info->hide(); + CanvasItemEditor::get_singleton()->add_control_to_info_overlay(tile_info); + + // Menu. + options = memnew(MenuButton); + options->set_text("RTileMap"); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("TileMap", "EditorIcons")); + options->set_process_unhandled_key_input(false); + toolbar_right->add_child(options); + + PopupMenu *p = options->get_popup(); + p->add_shortcut(ED_SHORTCUT("tile_map_editor/cut_selection", TTR("Cut Selection"), KEY_MASK_CMD + KEY_X), OPTION_CUT); + p->add_shortcut(ED_SHORTCUT("tile_map_editor/copy_selection", TTR("Copy Selection"), KEY_MASK_CMD + KEY_C), OPTION_COPY); + p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION); + p->add_separator(); + p->add_item(TTR("Fix Invalid Tiles"), OPTION_FIX_INVALID); + p->connect("id_pressed", this, "_menu_option"); + + rotate_left_button = memnew(ToolButton); + rotate_left_button->set_tooltip(TTR("Rotate Left")); + rotate_left_button->set_focus_mode(FOCUS_NONE); + rotate_left_button->connect("pressed", this, "_rotate", varray(-1)); + rotate_left_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_left", TTR("Rotate Left"), KEY_A)); + tool_hb->add_child(rotate_left_button); + + rotate_right_button = memnew(ToolButton); + rotate_right_button->set_tooltip(TTR("Rotate Right")); + rotate_right_button->set_focus_mode(FOCUS_NONE); + rotate_right_button->connect("pressed", this, "_rotate", varray(1)); + rotate_right_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_right", TTR("Rotate Right"), KEY_S)); + tool_hb->add_child(rotate_right_button); + + flip_horizontal_button = memnew(ToolButton); + flip_horizontal_button->set_tooltip(TTR("Flip Horizontally")); + flip_horizontal_button->set_focus_mode(FOCUS_NONE); + flip_horizontal_button->connect("pressed", this, "_flip_horizontal"); + flip_horizontal_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_horizontal", TTR("Flip Horizontally"), KEY_X)); + tool_hb->add_child(flip_horizontal_button); + + flip_vertical_button = memnew(ToolButton); + flip_vertical_button->set_tooltip(TTR("Flip Vertically")); + flip_vertical_button->set_focus_mode(FOCUS_NONE); + flip_vertical_button->connect("pressed", this, "_flip_vertical"); + flip_vertical_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_vertical", TTR("Flip Vertically"), KEY_Z)); + tool_hb->add_child(flip_vertical_button); + + clear_transform_button = memnew(ToolButton); + clear_transform_button->set_tooltip(TTR("Clear Transform")); + clear_transform_button->set_focus_mode(FOCUS_NONE); + clear_transform_button->connect("pressed", this, "_clear_transform"); + clear_transform_button->set_shortcut(ED_SHORTCUT("tile_map_editor/clear_transform", TTR("Clear Transform"), KEY_W)); + tool_hb->add_child(clear_transform_button); + + clear_transform_button->set_disabled(true); +} + +RTileMapEditor::~RTileMapEditor() { + _clear_bucket_cache(); + copydata.clear(); +} + +/////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////// + +void RTileMapEditorPlugin::_notification(int p_what) { + if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { + switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) { + case 0: { // Left. + CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 0); + } break; + case 1: { // Right. + CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 1); + } break; + } + } +} + +void RTileMapEditorPlugin::edit(Object *p_object) { + tile_map_editor->edit(Object::cast_to(p_object)); +} + +bool RTileMapEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("RTileMap"); +} + +void RTileMapEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + tile_map_editor->show(); + tile_map_editor->get_toolbar()->show(); + tile_map_editor->get_toolbar_right()->show(); + // `tile_info` isn't shown here, as it's displayed after a tile has been hovered. + // Otherwise, a translucent black rectangle would be visible as there would be an + // empty Label in the CanvasItemEditor's info overlay. + + // Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement. + CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT); + } else { + tile_map_editor->hide(); + tile_map_editor->get_toolbar()->hide(); + tile_map_editor->get_toolbar_right()->hide(); + tile_map_editor->get_tile_info()->hide(); + tile_map_editor->edit(nullptr); + } +} + +RTileMapEditorPlugin::RTileMapEditorPlugin(EditorNode *p_node) { + EDITOR_DEF("editors/tile_map/preview_size", 64); + EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); + EDITOR_DEF("editors/tile_map/show_tile_names", true); + EDITOR_DEF("editors/tile_map/show_tile_ids", false); + EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true); + EDITOR_DEF("editors/tile_map/bucket_fill_preview", true); + EDITOR_DEF("editors/tile_map/editor_side", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); + + tile_map_editor = memnew(RTileMapEditor(p_node)); + switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) { + case 0: { // Left. + add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_LEFT, tile_map_editor); + } break; + case 1: { // Right. + add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_RIGHT, tile_map_editor); + } break; + } + tile_map_editor->hide(); +} + +RTileMapEditorPlugin::~RTileMapEditorPlugin() { +} diff --git a/tile_map_editor_plugin.h b/tile_map_editor_plugin.h new file mode 100644 index 0000000..4af85b8 --- /dev/null +++ b/tile_map_editor_plugin.h @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* tile_map_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RTILE_MAP_EDITOR_PLUGIN_H +#define RTILE_MAP_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" + +#include "tile_map.h" +#include "scene/gui/check_box.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/tool_button.h" + +class RTileMapEditor : public VBoxContainer { + GDCLASS(RTileMapEditor, VBoxContainer); + + enum Tool { + + TOOL_NONE, + TOOL_PAINTING, + TOOL_ERASING, + TOOL_RECTANGLE_PAINT, + TOOL_RECTANGLE_ERASE, + TOOL_LINE_PAINT, + TOOL_LINE_ERASE, + TOOL_SELECTING, + TOOL_BUCKET, + TOOL_PICKING, + TOOL_PASTING + }; + + enum Options { + + OPTION_COPY, + OPTION_ERASE_SELECTION, + OPTION_FIX_INVALID, + OPTION_CUT + }; + + RTileMap *node; + bool manual_autotile; + bool priority_atlastile; + Vector2 manual_position; + + EditorNode *editor; + UndoRedo *undo_redo; + Control *canvas_item_editor_viewport; + + LineEdit *search_box; + HSlider *size_slider; + ItemList *palette; + ItemList *manual_palette; + + Label *info_message; + + HBoxContainer *toolbar; + HBoxContainer *toolbar_right; + + Label *tile_info; + MenuButton *options; + + ToolButton *paint_button; + ToolButton *bucket_fill_button; + ToolButton *picker_button; + ToolButton *select_button; + + ToolButton *flip_horizontal_button; + ToolButton *flip_vertical_button; + ToolButton *rotate_left_button; + ToolButton *rotate_right_button; + ToolButton *clear_transform_button; + + CheckBox *manual_button; + CheckBox *priority_button; + + Tool tool; + Tool last_tool; + + bool selection_active; + bool mouse_over; + + bool flip_h; + bool flip_v; + bool transpose; + Point2i autotile_coord; + + Point2i rectangle_begin; + Rect2i rectangle; + + Point2i over_tile; + bool refocus_over_tile; + + bool *bucket_cache_visited; + Rect2i bucket_cache_rect; + int bucket_cache_tile; + PoolVector bucket_cache; + List bucket_queue; + + struct CellOp { + int idx; + bool xf; + bool yf; + bool tr; + Vector2 ac; + + CellOp() : + idx(RTileMap::INVALID_CELL), + xf(false), + yf(false), + tr(false) {} + }; + + Map paint_undo; + + struct TileData { + Point2i pos; + int cell; + bool flip_h; + bool flip_v; + bool transpose; + Point2i autotile_coord; + + TileData() : + cell(RTileMap::INVALID_CELL), + flip_h(false), + flip_v(false), + transpose(false) {} + }; + + List copydata; + + Map undo_data; + Vector invalid_cell; + + void _pick_tile(const Point2 &p_pos); + + PoolVector _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false); + + void _fill_points(const PoolVector &p_points, const Dictionary &p_op); + void _erase_points(const PoolVector &p_points); + + void _select(const Point2i &p_from, const Point2i &p_to); + void _erase_selection(); + + void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); + void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); + void _clear_bucket_cache(); + + void _update_copydata(); + + Vector get_selected_tiles() const; + void set_selected_tiles(Vector p_tile); + + void _manual_toggled(bool p_enabled); + void _priority_toggled(bool p_enabled); + void _text_entered(const String &p_text); + void _text_changed(const String &p_text); + void _sbox_input(const Ref &p_ie); + void _update_palette(); + void _update_button_tool(); + void _button_tool_select(int p_tool); + void _menu_option(int p_option); + void _palette_selected(int index); + void _palette_multi_selected(int index, bool selected); + void _palette_input(const Ref &p_event); + + Dictionary _create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord); + void _start_undo(const String &p_action); + void _finish_undo(); + void _create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new); + void _set_cell(const Point2i &p_pos, Vector p_values, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, const Point2i &p_autotile_coord = Point2()); + + void _canvas_mouse_enter(); + void _canvas_mouse_exit(); + void _tileset_settings_changed(); + void _icon_size_changed(float p_value); + + void _clear_transform(); + void _flip_horizontal(); + void _flip_vertical(); + void _rotate(int steps); + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + static void _bind_methods(); + CellOp _get_op_from_cell(const Point2i &p_pos); + +public: + HBoxContainer *get_toolbar() const { return toolbar; } + HBoxContainer *get_toolbar_right() const { return toolbar_right; } + Label *get_tile_info() const { return tile_info; } + + bool forward_gui_input(const Ref &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + + void edit(Node *p_tile_map); + + RTileMapEditor(EditorNode *p_editor); + ~RTileMapEditor(); +}; + +class RTileMapEditorPlugin : public EditorPlugin { + GDCLASS(RTileMapEditorPlugin, EditorPlugin); + + RTileMapEditor *tile_map_editor; + +protected: + void _notification(int p_what); + +public: + virtual bool forward_canvas_gui_input(const Ref &p_event) { return tile_map_editor->forward_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) { tile_map_editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const { return "RTileMap"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + RTileMapEditorPlugin(EditorNode *p_node); + ~RTileMapEditorPlugin(); +}; + +#endif // TILE_MAP_EDITOR_PLUGIN_H diff --git a/tile_set.cpp b/tile_set.cpp new file mode 100644 index 0000000..38a63bd --- /dev/null +++ b/tile_set.cpp @@ -0,0 +1,1197 @@ +/*************************************************************************/ +/* tile_set.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set.h" +#include "core/array.h" +#include "core/engine.h" + +bool RTileSet::_set(const StringName &p_name, const Variant &p_value) { + String n = p_name; + int slash = n.find("/"); + if (slash == -1) { + return false; + } + int id = String::to_int(n.c_str(), slash); + + if (!tile_map.has(id)) { + create_tile(id); + } + String what = n.substr(slash + 1, n.length()); + + if (what == "name") { + tile_set_name(id, p_value); + } else if (what == "texture") { + tile_set_texture(id, p_value); + } else if (what == "normal_map") { + tile_set_normal_map(id, p_value); + } else if (what == "tex_offset") { + tile_set_texture_offset(id, p_value); + } else if (what == "material") { + tile_set_material(id, p_value); + } else if (what == "modulate") { + tile_set_modulate(id, p_value); + } else if (what == "region") { + tile_set_region(id, p_value); + } else if (what == "tile_mode") { + tile_set_tile_mode(id, (TileMode)((int)p_value)); + } else if (what == "is_autotile") { + // backward compatibility for Godot 3.0.x + // autotile used to be a bool, it's now an enum + bool is_autotile = p_value; + if (is_autotile) { + tile_set_tile_mode(id, AUTO_TILE); + } + } else if (what.left(9) == "autotile/") { + what = what.right(9); + if (what == "bitmask_mode") { + autotile_set_bitmask_mode(id, (BitmaskMode)((int)p_value)); + } else if (what == "icon_coordinate") { + autotile_set_icon_coordinate(id, p_value); + } else if (what == "tile_size") { + autotile_set_size(id, p_value); + } else if (what == "spacing") { + autotile_set_spacing(id, p_value); + } else if (what == "bitmask_flags") { + tile_map[id].autotile_data.flags.clear(); + if (p_value.is_array()) { + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::INT) { + autotile_set_bitmask(id, last_coord, p[0]); + } + p.pop_front(); + } + } + } else if (what == "occluder_map") { + tile_map[id].autotile_data.occluder_map.clear(); + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + autotile_set_light_occluder(id, p[0], last_coord); + } + p.pop_front(); + } + } else if (what == "navpoly_map") { + tile_map[id].autotile_data.navpoly_map.clear(); + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + autotile_set_navigation_polygon(id, p[0], last_coord); + } + p.pop_front(); + } + } else if (what == "priority_map") { + tile_map[id].autotile_data.priority_map.clear(); + Array p = p_value; + Vector3 val; + Vector2 v; + int priority; + while (p.size() > 0) { + val = p[0]; + if (val.z > 1) { + v.x = val.x; + v.y = val.y; + priority = (int)val.z; + tile_map[id].autotile_data.priority_map[v] = priority; + } + p.pop_front(); + } + } else if (what == "z_index_map") { + tile_map[id].autotile_data.z_index_map.clear(); + Array p = p_value; + Vector3 val; + Vector2 v; + int z_index; + while (p.size() > 0) { + val = p[0]; + if (val.z != 0) { + v.x = val.x; + v.y = val.y; + z_index = (int)val.z; + tile_map[id].autotile_data.z_index_map[v] = z_index; + } + p.pop_front(); + } + } + } else if (what == "shape") { + if (tile_get_shape_count(id) > 0) { + for (int i = 0; i < tile_get_shape_count(id); i++) { + tile_set_shape(id, i, p_value); + } + } else { + tile_set_shape(id, 0, p_value); + } + } else if (what == "shape_offset") { + if (tile_get_shape_count(id) > 0) { + for (int i = 0; i < tile_get_shape_count(id); i++) { + tile_set_shape_offset(id, i, p_value); + } + } else { + tile_set_shape_offset(id, 0, p_value); + } + } else if (what == "shape_transform") { + if (tile_get_shape_count(id) > 0) { + for (int i = 0; i < tile_get_shape_count(id); i++) { + tile_set_shape_transform(id, i, p_value); + } + } else { + tile_set_shape_transform(id, 0, p_value); + } + } else if (what == "shape_one_way") { + if (tile_get_shape_count(id) > 0) { + for (int i = 0; i < tile_get_shape_count(id); i++) { + tile_set_shape_one_way(id, i, p_value); + } + } else { + tile_set_shape_one_way(id, 0, p_value); + } + } else if (what == "shape_one_way_margin") { + if (tile_get_shape_count(id) > 0) { + for (int i = 0; i < tile_get_shape_count(id); i++) { + tile_set_shape_one_way_margin(id, i, p_value); + } + } else { + tile_set_shape_one_way_margin(id, 0, p_value); + } + } else if (what == "shapes") { + _tile_set_shapes(id, p_value); + } else if (what == "occluder") { + tile_set_light_occluder(id, p_value); + } else if (what == "occluder_offset") { + tile_set_occluder_offset(id, p_value); + } else if (what == "navigation") { + tile_set_navigation_polygon(id, p_value); + } else if (what == "navigation_offset") { + tile_set_navigation_polygon_offset(id, p_value); + } else if (what == "z_index") { + tile_set_z_index(id, p_value); + } else { + return false; + } + + return true; +} + +bool RTileSet::_get(const StringName &p_name, Variant &r_ret) const { + String n = p_name; + int slash = n.find("/"); + if (slash == -1) { + return false; + } + int id = String::to_int(n.c_str(), slash); + + ERR_FAIL_COND_V(!tile_map.has(id), false); + + String what = n.substr(slash + 1, n.length()); + + if (what == "name") { + r_ret = tile_get_name(id); + } else if (what == "texture") { + r_ret = tile_get_texture(id); + } else if (what == "normal_map") { + r_ret = tile_get_normal_map(id); + } else if (what == "tex_offset") { + r_ret = tile_get_texture_offset(id); + } else if (what == "material") { + r_ret = tile_get_material(id); + } else if (what == "modulate") { + r_ret = tile_get_modulate(id); + } else if (what == "region") { + r_ret = tile_get_region(id); + } else if (what == "tile_mode") { + r_ret = tile_get_tile_mode(id); + } else if (what.left(9) == "autotile/") { + what = what.right(9); + if (what == "bitmask_mode") { + r_ret = autotile_get_bitmask_mode(id); + } else if (what == "icon_coordinate") { + r_ret = autotile_get_icon_coordinate(id); + } else if (what == "tile_size") { + r_ret = autotile_get_size(id); + } else if (what == "spacing") { + r_ret = autotile_get_spacing(id); + } else if (what == "bitmask_flags") { + Array p; + for (Map::Element *E = tile_map[id].autotile_data.flags.front(); E; E = E->next()) { + p.push_back(E->key()); + p.push_back(E->value()); + } + r_ret = p; + } else if (what == "occluder_map") { + Array p; + for (Map>::Element *E = tile_map[id].autotile_data.occluder_map.front(); E; E = E->next()) { + p.push_back(E->key()); + p.push_back(E->value()); + } + r_ret = p; + } else if (what == "navpoly_map") { + Array p; + for (Map>::Element *E = tile_map[id].autotile_data.navpoly_map.front(); E; E = E->next()) { + p.push_back(E->key()); + p.push_back(E->value()); + } + r_ret = p; + } else if (what == "priority_map") { + Array p; + Vector3 v; + for (Map::Element *E = tile_map[id].autotile_data.priority_map.front(); E; E = E->next()) { + if (E->value() > 1) { + //Don't save default value + v.x = E->key().x; + v.y = E->key().y; + v.z = E->value(); + p.push_back(v); + } + } + r_ret = p; + } else if (what == "z_index_map") { + Array p; + Vector3 v; + for (Map::Element *E = tile_map[id].autotile_data.z_index_map.front(); E; E = E->next()) { + if (E->value() != 0) { + //Don't save default value + v.x = E->key().x; + v.y = E->key().y; + v.z = E->value(); + p.push_back(v); + } + } + r_ret = p; + } + } else if (what == "shape") { + r_ret = tile_get_shape(id, 0); + } else if (what == "shape_offset") { + r_ret = tile_get_shape_offset(id, 0); + } else if (what == "shape_transform") { + r_ret = tile_get_shape_transform(id, 0); + } else if (what == "shape_one_way") { + r_ret = tile_get_shape_one_way(id, 0); + } else if (what == "shape_one_way_margin") { + r_ret = tile_get_shape_one_way_margin(id, 0); + } else if (what == "shapes") { + r_ret = _tile_get_shapes(id); + } else if (what == "occluder") { + r_ret = tile_get_light_occluder(id); + } else if (what == "occluder_offset") { + r_ret = tile_get_occluder_offset(id); + } else if (what == "navigation") { + r_ret = tile_get_navigation_polygon(id); + } else if (what == "navigation_offset") { + r_ret = tile_get_navigation_polygon_offset(id); + } else if (what == "z_index") { + r_ret = tile_get_z_index(id); + } else { + return false; + } + + return true; +} + +void RTileSet::_get_property_list(List *p_list) const { + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + int id = E->key(); + String pre = itos(id) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, pre + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "tex_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::COLOR, pre + "modulate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE", PROPERTY_USAGE_NOEDITOR)); + if (tile_get_tile_mode(id) == AUTO_TILE) { + p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/bitmask_flags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + } else if (tile_get_tile_mode(id) == ATLAS_TILE) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "occluder_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "navigation_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "navigation", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, pre + "shape_one_way", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::REAL, pre + "shape_one_way_margin", PROPERTY_HINT_RANGE, "0,128,0.01", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "shapes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "z_index", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1", PROPERTY_USAGE_NOEDITOR)); + } +} + +void RTileSet::create_tile(int p_id) { + ERR_FAIL_COND_MSG(tile_map.has(p_id), vformat("The RTileSet already has a tile with ID '%d'.", p_id)); + tile_map[p_id] = TileData(); + tile_map[p_id].autotile_data = AutotileData(); + _change_notify(""); + emit_changed(); +} + +void RTileSet::autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].autotile_data.bitmask_mode = p_mode; + _change_notify(""); + emit_changed(); +} + +RTileSet::BitmaskMode RTileSet::autotile_get_bitmask_mode(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), BITMASK_2X2, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.bitmask_mode; +} + +void RTileSet::tile_set_texture(int p_id, const Ref &p_texture) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].texture = p_texture; + emit_changed(); + _change_notify("texture"); +} + +Ref RTileSet::tile_get_texture(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].texture; +} + +void RTileSet::tile_set_normal_map(int p_id, const Ref &p_normal_map) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].normal_map = p_normal_map; + emit_changed(); +} + +Ref RTileSet::tile_get_normal_map(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].normal_map; +} + +void RTileSet::tile_set_material(int p_id, const Ref &p_material) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].material = p_material; + emit_changed(); +} + +Ref RTileSet::tile_get_material(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].material; +} + +void RTileSet::tile_set_modulate(int p_id, const Color &p_modulate) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].modulate = p_modulate; + emit_changed(); + _change_notify("modulate"); +} + +Color RTileSet::tile_get_modulate(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Color(1, 1, 1), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].modulate; +} + +void RTileSet::tile_set_texture_offset(int p_id, const Vector2 &p_offset) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].offset = p_offset; + emit_changed(); +} + +Vector2 RTileSet::tile_get_texture_offset(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].offset; +} + +void RTileSet::tile_set_region(int p_id, const Rect2 &p_region) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].region = p_region; + emit_changed(); + _change_notify("region"); +} + +Rect2 RTileSet::tile_get_region(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Rect2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].region; +} + +void RTileSet::tile_set_tile_mode(int p_id, TileMode p_tile_mode) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].tile_mode = p_tile_mode; + emit_changed(); + _change_notify("tile_mode"); +} + +RTileSet::TileMode RTileSet::tile_get_tile_mode(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), SINGLE_TILE, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].tile_mode; +} + +void RTileSet::autotile_set_icon_coordinate(int p_id, Vector2 coord) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].autotile_data.icon_coord = coord; + emit_changed(); +} + +Vector2 RTileSet::autotile_get_icon_coordinate(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.icon_coord; +} + +void RTileSet::autotile_set_spacing(int p_id, int p_spacing) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_spacing < 0); + tile_map[p_id].autotile_data.spacing = p_spacing; + emit_changed(); +} + +int RTileSet::autotile_get_spacing(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 0, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.spacing; +} + +void RTileSet::autotile_set_size(int p_id, Size2 p_size) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); + tile_map[p_id].autotile_data.size = p_size; +} + +Size2 RTileSet::autotile_get_size(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Size2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.size; +} + +void RTileSet::autotile_clear_bitmask_map(int p_id) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].autotile_data.flags.clear(); +} + +void RTileSet::autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_priority <= 0); + tile_map[p_id].autotile_data.priority_map[p_coord] = p_priority; +} + +int RTileSet::autotile_get_subtile_priority(int p_id, const Vector2 &p_coord) { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 1, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (tile_map[p_id].autotile_data.priority_map.has(p_coord)) { + return tile_map[p_id].autotile_data.priority_map[p_coord]; + } + //When not custom priority set return the default value + return 1; +} + +const Map &RTileSet::autotile_get_priority_map(int p_id) const { + static Map dummy; + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), dummy, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.priority_map; +} + +void RTileSet::autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].autotile_data.z_index_map[p_coord] = p_z_index; + emit_changed(); +} + +int RTileSet::autotile_get_z_index(int p_id, const Vector2 &p_coord) { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 1, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (tile_map[p_id].autotile_data.z_index_map.has(p_coord)) { + return tile_map[p_id].autotile_data.z_index_map[p_coord]; + } + //When not custom z index set return the default value + return 0; +} + +const Map &RTileSet::autotile_get_z_index_map(int p_id) const { + static Map dummy; + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), dummy, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.z_index_map; +} + +void RTileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (p_flag == 0) { + if (tile_map[p_id].autotile_data.flags.has(p_coord)) { + tile_map[p_id].autotile_data.flags.erase(p_coord); + } + } else { + tile_map[p_id].autotile_data.flags[p_coord] = p_flag; + } +} + +uint32_t RTileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 0, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (!tile_map[p_id].autotile_data.flags.has(p_coord)) { + return 0; + } + return tile_map[p_id].autotile_data.flags[p_coord]; +} + +const Map &RTileSet::autotile_get_bitmask_map(int p_id) { + static Map dummy; + static Map dummy_atlas; + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), dummy, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (tile_get_tile_mode(p_id) == ATLAS_TILE) { + dummy_atlas = Map(); + Rect2 region = tile_get_region(p_id); + Size2 size = autotile_get_size(p_id); + float spacing = autotile_get_spacing(p_id); + for (int x = 0; x < (region.size.x / (size.x + spacing)); x++) { + for (int y = 0; y < (region.size.y / (size.y + spacing)); y++) { + dummy_atlas.insert(Vector2(x, y), 0); + } + } + return dummy_atlas; + } else { + return tile_map[p_id].autotile_data.flags; + } +} + +Vector2 RTileSet::autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node, const Vector2 &p_tile_location) { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + //First try to forward selection to script + if (p_tilemap_node->get_class_name() == "TileMap") { + if (get_script_instance() != nullptr) { + if (get_script_instance()->has_method("_forward_subtile_selection")) { + Variant ret = get_script_instance()->call("_forward_subtile_selection", p_id, p_bitmask, p_tilemap_node, p_tile_location); + if (ret.get_type() == Variant::VECTOR2) { + return ret; + } + } + } + } + + List coords; + List priorities; + uint32_t priority_sum = 0; + uint32_t mask; + uint16_t mask_; + uint16_t mask_ignore; + for (Map::Element *E = tile_map[p_id].autotile_data.flags.front(); E; E = E->next()) { + mask = E->get(); + if (tile_map[p_id].autotile_data.bitmask_mode == BITMASK_2X2) { + mask |= (BIND_IGNORE_TOP | BIND_IGNORE_LEFT | BIND_IGNORE_CENTER | BIND_IGNORE_RIGHT | BIND_IGNORE_BOTTOM); + } + + mask_ = mask & 0xFFFF; + mask_ignore = mask >> 16; + + if (((mask_ & (~mask_ignore)) == (p_bitmask & (~mask_ignore))) && (((~mask_) | mask_ignore) == ((~p_bitmask) | mask_ignore))) { + uint32_t priority = autotile_get_subtile_priority(p_id, E->key()); + priority_sum += priority; + priorities.push_back(priority); + coords.push_back(E->key()); + } + } + + if (coords.size() == 0) { + return autotile_get_icon_coordinate(p_id); + } else { + uint32_t picked_value = Math::rand() % priority_sum; + uint32_t upper_bound; + uint32_t lower_bound = 0; + Vector2 result = coords.front()->get(); + List::Element *coords_E = coords.front(); + List::Element *priorities_E = priorities.front(); + while (priorities_E) { + upper_bound = lower_bound + priorities_E->get(); + if (lower_bound <= picked_value && picked_value < upper_bound) { + result = coords_E->get(); + break; + } + lower_bound = upper_bound; + priorities_E = priorities_E->next(); + coords_E = coords_E->next(); + } + + return result; + } +} + +Vector2 RTileSet::atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node, const Vector2 &p_tile_location) { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + //First try to forward selection to script + if (get_script_instance() != nullptr) { + if (get_script_instance()->has_method("_forward_atlas_subtile_selection")) { + Variant ret = get_script_instance()->call("_forward_atlas_subtile_selection", p_id, p_tilemap_node, p_tile_location); + if (ret.get_type() == Variant::VECTOR2) { + return ret; + } + } + } + + const Vector2 spacing(autotile_get_spacing(p_id), autotile_get_spacing(p_id)); + const Vector2 coord = tile_get_region(p_id).size / (autotile_get_size(p_id) + spacing); + + List coords; + for (int x = 0; x < coord.x; x++) { + for (int y = 0; y < coord.y; y++) { + for (int i = 0; i < autotile_get_subtile_priority(p_id, Vector2(x, y)); i++) { + coords.push_back(Vector2(x, y)); + } + } + } + if (coords.size() == 0) { + return autotile_get_icon_coordinate(p_id); + } else { + return coords[Math::random(0, (int)coords.size())]; + } +} + +void RTileSet::tile_set_name(int p_id, const String &p_name) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].name = p_name; + emit_changed(); + _change_notify("name"); +} + +String RTileSet::tile_get_name(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), String(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].name; +} + +void RTileSet::tile_clear_shapes(int p_id) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].shapes_data.clear(); +} + +void RTileSet::tile_add_shape(int p_id, const Ref &p_shape, const Transform2D &p_transform, bool p_one_way, const Vector2 &p_autotile_coord) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + + ShapeData new_data = ShapeData(); + new_data.shape = p_shape; + new_data.shape_transform = p_transform; + new_data.one_way_collision = p_one_way; + new_data.autotile_coord = p_autotile_coord; + + tile_map[p_id].shapes_data.push_back(new_data); +} + +int RTileSet::tile_get_shape_count(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 0, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].shapes_data.size(); +} + +void RTileSet::tile_set_shape(int p_id, int p_shape_id, const Ref &p_shape) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) { + tile_map[p_id].shapes_data.resize(p_shape_id + 1); + } + tile_map[p_id].shapes_data.write[p_shape_id].shape = p_shape; + _decompose_convex_shape(p_shape); + emit_changed(); +} + +Ref RTileSet::tile_get_shape(int p_id, int p_shape_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND_V(p_shape_id < 0, Ref()); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) { + return tile_map[p_id].shapes_data[p_shape_id].shape; + } + + return Ref(); +} + +void RTileSet::tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) { + tile_map[p_id].shapes_data.resize(p_shape_id + 1); + } + tile_map[p_id].shapes_data.write[p_shape_id].shape_transform = p_offset; + emit_changed(); +} + +Transform2D RTileSet::tile_get_shape_transform(int p_id, int p_shape_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Transform2D(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND_V(p_shape_id < 0, Transform2D()); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) { + return tile_map[p_id].shapes_data[p_shape_id].shape_transform; + } + + return Transform2D(); +} + +void RTileSet::tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset) { + Transform2D transform = tile_get_shape_transform(p_id, p_shape_id); + transform.set_origin(p_offset); + tile_set_shape_transform(p_id, p_shape_id, transform); +} + +Vector2 RTileSet::tile_get_shape_offset(int p_id, int p_shape_id) const { + return tile_get_shape_transform(p_id, p_shape_id).get_origin(); +} + +void RTileSet::tile_set_shape_one_way(int p_id, int p_shape_id, const bool p_one_way) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) { + tile_map[p_id].shapes_data.resize(p_shape_id + 1); + } + tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision = p_one_way; + emit_changed(); +} + +bool RTileSet::tile_get_shape_one_way(int p_id, int p_shape_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), false, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND_V(p_shape_id < 0, false); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) { + return tile_map[p_id].shapes_data[p_shape_id].one_way_collision; + } + + return false; +} + +void RTileSet::tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) { + tile_map[p_id].shapes_data.resize(p_shape_id + 1); + } + tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision_margin = p_margin; + emit_changed(); +} + +float RTileSet::tile_get_shape_one_way_margin(int p_id, int p_shape_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 0, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + ERR_FAIL_COND_V(p_shape_id < 0, 0); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) { + return tile_map[p_id].shapes_data[p_shape_id].one_way_collision_margin; + } + + return 0; +} + +void RTileSet::tile_set_light_occluder(int p_id, const Ref &p_light_occluder) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].occluder = p_light_occluder; +} + +Ref RTileSet::tile_get_light_occluder(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].occluder; +} + +void RTileSet::autotile_set_light_occluder(int p_id, const Ref &p_light_occluder, const Vector2 &p_coord) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (p_light_occluder.is_null()) { + if (tile_map[p_id].autotile_data.occluder_map.has(p_coord)) { + tile_map[p_id].autotile_data.occluder_map.erase(p_coord); + } + } else { + tile_map[p_id].autotile_data.occluder_map[p_coord] = p_light_occluder; + } +} + +Ref RTileSet::autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + + if (!tile_map[p_id].autotile_data.occluder_map.has(p_coord)) { + return Ref(); + } else { + return tile_map[p_id].autotile_data.occluder_map[p_coord]; + } +} + +void RTileSet::tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].navigation_polygon_offset = p_offset; +} + +Vector2 RTileSet::tile_get_navigation_polygon_offset(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].navigation_polygon_offset; +} + +void RTileSet::tile_set_navigation_polygon(int p_id, const Ref &p_navigation_polygon) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].navigation_polygon = p_navigation_polygon; +} + +Ref RTileSet::tile_get_navigation_polygon(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].navigation_polygon; +} + +const Map> &RTileSet::autotile_get_light_oclusion_map(int p_id) const { + static Map> dummy; + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), dummy, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.occluder_map; +} + +void RTileSet::autotile_set_navigation_polygon(int p_id, const Ref &p_navigation_polygon, const Vector2 &p_coord) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (p_navigation_polygon.is_null()) { + if (tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) { + tile_map[p_id].autotile_data.navpoly_map.erase(p_coord); + } + } else { + tile_map[p_id].autotile_data.navpoly_map[p_coord] = p_navigation_polygon; + } +} + +Ref RTileSet::autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Ref(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + if (!tile_map[p_id].autotile_data.navpoly_map.has(p_coord)) { + return Ref(); + } else { + return tile_map[p_id].autotile_data.navpoly_map[p_coord]; + } +} + +const Map> &RTileSet::autotile_get_navigation_map(int p_id) const { + static Map> dummy; + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), dummy, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].autotile_data.navpoly_map; +} + +void RTileSet::tile_set_occluder_offset(int p_id, const Vector2 &p_offset) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].occluder_offset = p_offset; +} + +Vector2 RTileSet::tile_get_occluder_offset(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector2(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].occluder_offset; +} + +void RTileSet::tile_set_shapes(int p_id, const Vector &p_shapes) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].shapes_data = p_shapes; + for (int i = 0; i < p_shapes.size(); i++) { + _decompose_convex_shape(p_shapes[i].shape); + } + emit_changed(); +} + +Vector RTileSet::tile_get_shapes(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), Vector(), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + + return tile_map[p_id].shapes_data; +} + +int RTileSet::tile_get_z_index(int p_id) const { + ERR_FAIL_COND_V_MSG(!tile_map.has(p_id), 0, vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + return tile_map[p_id].z_index; +} + +void RTileSet::tile_set_z_index(int p_id, int p_z_index) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map[p_id].z_index = p_z_index; + emit_changed(); +} + +void RTileSet::_tile_set_shapes(int p_id, const Array &p_shapes) { + ERR_FAIL_COND(!tile_map.has(p_id)); + Vector shapes_data; + Transform2D default_transform = tile_get_shape_transform(p_id, 0); + bool default_one_way = tile_get_shape_one_way(p_id, 0); + Vector2 default_autotile_coord = Vector2(); + for (int i = 0; i < p_shapes.size(); i++) { + ShapeData s = ShapeData(); + + if (p_shapes[i].get_type() == Variant::OBJECT) { + Ref shape = p_shapes[i]; + if (shape.is_null()) { + continue; + } + + s.shape = shape; + s.shape_transform = default_transform; + s.one_way_collision = default_one_way; + s.autotile_coord = default_autotile_coord; + } else if (p_shapes[i].get_type() == Variant::DICTIONARY) { + Dictionary d = p_shapes[i]; + + if (d.has("shape") && d["shape"].get_type() == Variant::OBJECT) { + s.shape = d["shape"]; + _decompose_convex_shape(s.shape); + } else { + continue; + } + + if (d.has("shape_transform") && d["shape_transform"].get_type() == Variant::TRANSFORM2D) { + s.shape_transform = d["shape_transform"]; + } else if (d.has("shape_offset") && d["shape_offset"].get_type() == Variant::VECTOR2) { + s.shape_transform = Transform2D(0, (Vector2)d["shape_offset"]); + } else { + s.shape_transform = default_transform; + } + + if (d.has("one_way") && d["one_way"].get_type() == Variant::BOOL) { + s.one_way_collision = d["one_way"]; + } else { + s.one_way_collision = default_one_way; + } + + if (d.has("one_way_margin") && d["one_way_margin"].is_num()) { + s.one_way_collision_margin = d["one_way_margin"]; + } else { + s.one_way_collision_margin = 1.0; + } + + if (d.has("autotile_coord") && d["autotile_coord"].get_type() == Variant::VECTOR2) { + s.autotile_coord = d["autotile_coord"]; + } else { + s.autotile_coord = default_autotile_coord; + } + + } else { + ERR_CONTINUE_MSG(true, "Expected an array of objects or dictionaries for tile_set_shapes."); + } + + shapes_data.push_back(s); + } + + tile_map[p_id].shapes_data = shapes_data; + emit_changed(); +} + +Array RTileSet::_tile_get_shapes(int p_id) const { + ERR_FAIL_COND_V(!tile_map.has(p_id), Array()); + Array arr; + + Vector data = tile_map[p_id].shapes_data; + for (int i = 0; i < data.size(); i++) { + Dictionary shape_data; + shape_data["shape"] = data[i].shape; + shape_data["shape_transform"] = data[i].shape_transform; + shape_data["one_way"] = data[i].one_way_collision; + shape_data["one_way_margin"] = data[i].one_way_collision_margin; + shape_data["autotile_coord"] = data[i].autotile_coord; + arr.push_back(shape_data); + } + + return arr; +} + +Array RTileSet::_get_tiles_ids() const { + Array arr; + + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + arr.push_back(E->key()); + } + + return arr; +} + +void RTileSet::_decompose_convex_shape(Ref p_shape) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + Ref convex = p_shape; + if (!convex.is_valid()) { + return; + } + Vector> decomp = Geometry::decompose_polygon_in_convex(convex->get_points()); + if (decomp.size() > 1) { + Array sub_shapes; + for (int i = 0; i < decomp.size(); i++) { + Ref _convex = memnew(ConvexPolygonShape2D); + _convex->set_points(decomp[i]); + sub_shapes.append(_convex); + } + convex->set_meta("decomposed", sub_shapes); + } else { + convex->set_meta("decomposed", Variant()); + } +} + +void RTileSet::get_tile_list(List *p_tiles) const { + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + p_tiles->push_back(E->key()); + } +} + +bool RTileSet::has_tile(int p_id) const { + return tile_map.has(p_id); +} + +bool RTileSet::is_tile_bound(int p_drawn_id, int p_neighbor_id) { + if (p_drawn_id == p_neighbor_id) { + return true; + } else if (get_script_instance() != nullptr) { + if (get_script_instance()->has_method("_is_tile_bound")) { + Variant ret = get_script_instance()->call("_is_tile_bound", p_drawn_id, p_neighbor_id); + if (ret.get_type() == Variant::BOOL) { + return ret; + } + } + } + return false; +} + +void RTileSet::remove_tile(int p_id) { + ERR_FAIL_COND_MSG(!tile_map.has(p_id), vformat("The RTileSet doesn't have a tile with ID '%d'.", p_id)); + tile_map.erase(p_id); + _change_notify(""); + emit_changed(); +} + +int RTileSet::get_last_unused_tile_id() const { + if (tile_map.size()) { + return tile_map.back()->key() + 1; + } else { + return 0; + } +} + +int RTileSet::find_tile_by_name(const String &p_name) const { + for (Map::Element *E = tile_map.front(); E; E = E->next()) { + if (p_name == E->get().name) { + return E->key(); + } + } + return -1; +} + +void RTileSet::clear() { + tile_map.clear(); + _change_notify(""); + emit_changed(); +} + +void RTileSet::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_tile", "id"), &RTileSet::create_tile); + ClassDB::bind_method(D_METHOD("autotile_clear_bitmask_map", "id"), &RTileSet::autotile_clear_bitmask_map); + ClassDB::bind_method(D_METHOD("autotile_set_icon_coordinate", "id", "coord"), &RTileSet::autotile_set_icon_coordinate); + ClassDB::bind_method(D_METHOD("autotile_get_icon_coordinate", "id"), &RTileSet::autotile_get_icon_coordinate); + ClassDB::bind_method(D_METHOD("autotile_set_subtile_priority", "id", "coord", "priority"), &RTileSet::autotile_set_subtile_priority); + ClassDB::bind_method(D_METHOD("autotile_get_subtile_priority", "id", "coord"), &RTileSet::autotile_get_subtile_priority); + ClassDB::bind_method(D_METHOD("autotile_set_z_index", "id", "coord", "z_index"), &RTileSet::autotile_set_z_index); + ClassDB::bind_method(D_METHOD("autotile_get_z_index", "id", "coord"), &RTileSet::autotile_get_z_index); + ClassDB::bind_method(D_METHOD("autotile_set_light_occluder", "id", "light_occluder", "coord"), &RTileSet::autotile_set_light_occluder); + ClassDB::bind_method(D_METHOD("autotile_get_light_occluder", "id", "coord"), &RTileSet::autotile_get_light_occluder); + ClassDB::bind_method(D_METHOD("autotile_set_navigation_polygon", "id", "navigation_polygon", "coord"), &RTileSet::autotile_set_navigation_polygon); + ClassDB::bind_method(D_METHOD("autotile_get_navigation_polygon", "id", "coord"), &RTileSet::autotile_get_navigation_polygon); + ClassDB::bind_method(D_METHOD("autotile_set_bitmask", "id", "bitmask", "flag"), &RTileSet::autotile_set_bitmask); + ClassDB::bind_method(D_METHOD("autotile_get_bitmask", "id", "coord"), &RTileSet::autotile_get_bitmask); + ClassDB::bind_method(D_METHOD("autotile_set_bitmask_mode", "id", "mode"), &RTileSet::autotile_set_bitmask_mode); + ClassDB::bind_method(D_METHOD("autotile_get_bitmask_mode", "id"), &RTileSet::autotile_get_bitmask_mode); + ClassDB::bind_method(D_METHOD("autotile_set_spacing", "id", "spacing"), &RTileSet::autotile_set_spacing); + ClassDB::bind_method(D_METHOD("autotile_get_spacing", "id"), &RTileSet::autotile_get_spacing); + ClassDB::bind_method(D_METHOD("autotile_set_size", "id", "size"), &RTileSet::autotile_set_size); + ClassDB::bind_method(D_METHOD("autotile_get_size", "id"), &RTileSet::autotile_get_size); + ClassDB::bind_method(D_METHOD("tile_set_name", "id", "name"), &RTileSet::tile_set_name); + ClassDB::bind_method(D_METHOD("tile_get_name", "id"), &RTileSet::tile_get_name); + ClassDB::bind_method(D_METHOD("tile_set_texture", "id", "texture"), &RTileSet::tile_set_texture); + ClassDB::bind_method(D_METHOD("tile_get_texture", "id"), &RTileSet::tile_get_texture); + ClassDB::bind_method(D_METHOD("tile_set_normal_map", "id", "normal_map"), &RTileSet::tile_set_normal_map); + ClassDB::bind_method(D_METHOD("tile_get_normal_map", "id"), &RTileSet::tile_get_normal_map); + ClassDB::bind_method(D_METHOD("tile_set_material", "id", "material"), &RTileSet::tile_set_material); + ClassDB::bind_method(D_METHOD("tile_get_material", "id"), &RTileSet::tile_get_material); + ClassDB::bind_method(D_METHOD("tile_set_modulate", "id", "color"), &RTileSet::tile_set_modulate); + ClassDB::bind_method(D_METHOD("tile_get_modulate", "id"), &RTileSet::tile_get_modulate); + ClassDB::bind_method(D_METHOD("tile_set_texture_offset", "id", "texture_offset"), &RTileSet::tile_set_texture_offset); + ClassDB::bind_method(D_METHOD("tile_get_texture_offset", "id"), &RTileSet::tile_get_texture_offset); + ClassDB::bind_method(D_METHOD("tile_set_region", "id", "region"), &RTileSet::tile_set_region); + ClassDB::bind_method(D_METHOD("tile_get_region", "id"), &RTileSet::tile_get_region); + ClassDB::bind_method(D_METHOD("tile_set_shape", "id", "shape_id", "shape"), &RTileSet::tile_set_shape); + ClassDB::bind_method(D_METHOD("tile_get_shape", "id", "shape_id"), &RTileSet::tile_get_shape); + ClassDB::bind_method(D_METHOD("tile_set_shape_offset", "id", "shape_id", "shape_offset"), &RTileSet::tile_set_shape_offset); + ClassDB::bind_method(D_METHOD("tile_get_shape_offset", "id", "shape_id"), &RTileSet::tile_get_shape_offset); + ClassDB::bind_method(D_METHOD("tile_set_shape_transform", "id", "shape_id", "shape_transform"), &RTileSet::tile_set_shape_transform); + ClassDB::bind_method(D_METHOD("tile_get_shape_transform", "id", "shape_id"), &RTileSet::tile_get_shape_transform); + ClassDB::bind_method(D_METHOD("tile_set_shape_one_way", "id", "shape_id", "one_way"), &RTileSet::tile_set_shape_one_way); + ClassDB::bind_method(D_METHOD("tile_get_shape_one_way", "id", "shape_id"), &RTileSet::tile_get_shape_one_way); + ClassDB::bind_method(D_METHOD("tile_set_shape_one_way_margin", "id", "shape_id", "one_way"), &RTileSet::tile_set_shape_one_way_margin); + ClassDB::bind_method(D_METHOD("tile_get_shape_one_way_margin", "id", "shape_id"), &RTileSet::tile_get_shape_one_way_margin); + ClassDB::bind_method(D_METHOD("tile_add_shape", "id", "shape", "shape_transform", "one_way", "autotile_coord"), &RTileSet::tile_add_shape, DEFVAL(false), DEFVAL(Vector2())); + ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &RTileSet::tile_get_shape_count); + ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &RTileSet::_tile_set_shapes); + ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &RTileSet::_tile_get_shapes); + ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &RTileSet::tile_set_tile_mode); + ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &RTileSet::tile_get_tile_mode); + ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &RTileSet::tile_set_navigation_polygon); + ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &RTileSet::tile_get_navigation_polygon); + ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &RTileSet::tile_set_navigation_polygon_offset); + ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon_offset", "id"), &RTileSet::tile_get_navigation_polygon_offset); + ClassDB::bind_method(D_METHOD("tile_set_light_occluder", "id", "light_occluder"), &RTileSet::tile_set_light_occluder); + ClassDB::bind_method(D_METHOD("tile_get_light_occluder", "id"), &RTileSet::tile_get_light_occluder); + ClassDB::bind_method(D_METHOD("tile_set_occluder_offset", "id", "occluder_offset"), &RTileSet::tile_set_occluder_offset); + ClassDB::bind_method(D_METHOD("tile_get_occluder_offset", "id"), &RTileSet::tile_get_occluder_offset); + ClassDB::bind_method(D_METHOD("tile_set_z_index", "id", "z_index"), &RTileSet::tile_set_z_index); + ClassDB::bind_method(D_METHOD("tile_get_z_index", "id"), &RTileSet::tile_get_z_index); + + ClassDB::bind_method(D_METHOD("remove_tile", "id"), &RTileSet::remove_tile); + ClassDB::bind_method(D_METHOD("clear"), &RTileSet::clear); + ClassDB::bind_method(D_METHOD("get_last_unused_tile_id"), &RTileSet::get_last_unused_tile_id); + ClassDB::bind_method(D_METHOD("find_tile_by_name", "name"), &RTileSet::find_tile_by_name); + ClassDB::bind_method(D_METHOD("get_tiles_ids"), &RTileSet::_get_tiles_ids); + + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_tile_bound", PropertyInfo(Variant::INT, "drawn_id"), PropertyInfo(Variant::INT, "neighbor_id"))); + BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_subtile_selection", PropertyInfo(Variant::INT, "autotile_id"), PropertyInfo(Variant::INT, "bitmask"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "RTileMap"), PropertyInfo(Variant::VECTOR2, "tile_location"))); + BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_atlas_subtile_selection", PropertyInfo(Variant::INT, "atlastile_id"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "RTileMap"), PropertyInfo(Variant::VECTOR2, "tile_location"))); + + BIND_ENUM_CONSTANT(BITMASK_2X2); + BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL); + BIND_ENUM_CONSTANT(BITMASK_3X3); + + BIND_ENUM_CONSTANT(BIND_TOPLEFT); + BIND_ENUM_CONSTANT(BIND_TOP); + BIND_ENUM_CONSTANT(BIND_TOPRIGHT); + BIND_ENUM_CONSTANT(BIND_LEFT); + BIND_ENUM_CONSTANT(BIND_CENTER); + BIND_ENUM_CONSTANT(BIND_RIGHT); + BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT); + BIND_ENUM_CONSTANT(BIND_BOTTOM); + BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT); + + BIND_ENUM_CONSTANT(SINGLE_TILE); + BIND_ENUM_CONSTANT(AUTO_TILE); + BIND_ENUM_CONSTANT(ATLAS_TILE); +} + +RTileSet::RTileSet() { +} diff --git a/tile_set.h b/tile_set.h new file mode 100644 index 0000000..e0fa02f --- /dev/null +++ b/tile_set.h @@ -0,0 +1,269 @@ +/*************************************************************************/ +/* tile_set.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RTILE_SET_H +#define RTILE_SET_H + +#include "core/array.h" +#include "core/resource.h" +#include "scene/2d/light_occluder_2d.h" +#include "scene/2d/navigation_polygon.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/shape_2d.h" +#include "scene/resources/texture.h" + +class RTileSet : public Resource { + GDCLASS(RTileSet, Resource); + +public: + struct ShapeData { + Ref shape; + Transform2D shape_transform; + Vector2 autotile_coord; + bool one_way_collision; + float one_way_collision_margin; + + ShapeData() { + one_way_collision = false; + one_way_collision_margin = 1.0; + } + }; + + enum BitmaskMode { + BITMASK_2X2, + BITMASK_3X3_MINIMAL, + BITMASK_3X3 + }; + + enum AutotileBindings { + BIND_TOPLEFT = 1, + BIND_TOP = 2, + BIND_TOPRIGHT = 4, + BIND_LEFT = 8, + BIND_CENTER = 16, + BIND_RIGHT = 32, + BIND_BOTTOMLEFT = 64, + BIND_BOTTOM = 128, + BIND_BOTTOMRIGHT = 256, + + BIND_IGNORE_TOPLEFT = 1 << 16, + BIND_IGNORE_TOP = 1 << 17, + BIND_IGNORE_TOPRIGHT = 1 << 18, + BIND_IGNORE_LEFT = 1 << 19, + BIND_IGNORE_CENTER = 1 << 20, + BIND_IGNORE_RIGHT = 1 << 21, + BIND_IGNORE_BOTTOMLEFT = 1 << 22, + BIND_IGNORE_BOTTOM = 1 << 23, + BIND_IGNORE_BOTTOMRIGHT = 1 << 24 + }; + + enum TileMode { + SINGLE_TILE, + AUTO_TILE, + ATLAS_TILE + }; + + struct AutotileData { + BitmaskMode bitmask_mode; + Size2 size; + int spacing; + Vector2 icon_coord; + Map flags; + Map> occluder_map; + Map> navpoly_map; + Map priority_map; + Map z_index_map; + + // Default size to prevent invalid value + explicit AutotileData() : + bitmask_mode(BITMASK_2X2), + size(64, 64), + spacing(0), + icon_coord(0, 0) {} + }; + +private: + struct TileData { + String name; + Ref texture; + Ref normal_map; + Vector2 offset; + Rect2i region; + Vector shapes_data; + Vector2 occluder_offset; + Ref occluder; + Vector2 navigation_polygon_offset; + Ref navigation_polygon; + Ref material; + TileMode tile_mode; + Color modulate; + AutotileData autotile_data; + int z_index; + + // Default modulate for back-compat + explicit TileData() : + tile_mode(SINGLE_TILE), + modulate(1, 1, 1), + z_index(0) {} + }; + + Map tile_map; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + void _tile_set_shapes(int p_id, const Array &p_shapes); + Array _tile_get_shapes(int p_id) const; + Array _get_tiles_ids() const; + void _decompose_convex_shape(Ref p_shape); + + static void _bind_methods(); + +public: + void create_tile(int p_id); + + void autotile_set_bitmask_mode(int p_id, BitmaskMode p_mode); + BitmaskMode autotile_get_bitmask_mode(int p_id) const; + + void tile_set_name(int p_id, const String &p_name); + String tile_get_name(int p_id) const; + + void tile_set_texture(int p_id, const Ref &p_texture); + Ref tile_get_texture(int p_id) const; + + void tile_set_normal_map(int p_id, const Ref &p_normal_map); + Ref tile_get_normal_map(int p_id) const; + + void tile_set_texture_offset(int p_id, const Vector2 &p_offset); + Vector2 tile_get_texture_offset(int p_id) const; + + void tile_set_region(int p_id, const Rect2 &p_region); + Rect2 tile_get_region(int p_id) const; + + void tile_set_tile_mode(int p_id, TileMode p_tile_mode); + TileMode tile_get_tile_mode(int p_id) const; + + void autotile_set_icon_coordinate(int p_id, Vector2 coord); + Vector2 autotile_get_icon_coordinate(int p_id) const; + + void autotile_set_spacing(int p_id, int p_spacing); + int autotile_get_spacing(int p_id) const; + + void autotile_set_size(int p_id, Size2 p_size); + Size2 autotile_get_size(int p_id) const; + + void autotile_clear_bitmask_map(int p_id); + void autotile_set_subtile_priority(int p_id, const Vector2 &p_coord, int p_priority); + int autotile_get_subtile_priority(int p_id, const Vector2 &p_coord); + const Map &autotile_get_priority_map(int p_id) const; + + void autotile_set_z_index(int p_id, const Vector2 &p_coord, int p_z_index); + int autotile_get_z_index(int p_id, const Vector2 &p_coord); + const Map &autotile_get_z_index_map(int p_id) const; + + void autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag); + uint32_t autotile_get_bitmask(int p_id, Vector2 p_coord); + const Map &autotile_get_bitmask_map(int p_id); + Vector2 autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2()); + Vector2 atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node = nullptr, const Vector2 &p_tile_location = Vector2()); + + void tile_set_shape(int p_id, int p_shape_id, const Ref &p_shape); + Ref tile_get_shape(int p_id, int p_shape_id) const; + + void tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset); + Transform2D tile_get_shape_transform(int p_id, int p_shape_id) const; + + void tile_set_shape_offset(int p_id, int p_shape_id, const Vector2 &p_offset); + Vector2 tile_get_shape_offset(int p_id, int p_shape_id) const; + + void tile_set_shape_one_way(int p_id, int p_shape_id, bool p_one_way); + bool tile_get_shape_one_way(int p_id, int p_shape_id) const; + + void tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin); + float tile_get_shape_one_way_margin(int p_id, int p_shape_id) const; + + void tile_clear_shapes(int p_id); + void tile_add_shape(int p_id, const Ref &p_shape, const Transform2D &p_transform, bool p_one_way = false, const Vector2 &p_autotile_coord = Vector2()); + int tile_get_shape_count(int p_id) const; + + void tile_set_shapes(int p_id, const Vector &p_shapes); + Vector tile_get_shapes(int p_id) const; + + void tile_set_material(int p_id, const Ref &p_material); + Ref tile_get_material(int p_id) const; + + void tile_set_modulate(int p_id, const Color &p_modulate); + Color tile_get_modulate(int p_id) const; + + void tile_set_occluder_offset(int p_id, const Vector2 &p_offset); + Vector2 tile_get_occluder_offset(int p_id) const; + + void tile_set_light_occluder(int p_id, const Ref &p_light_occluder); + Ref tile_get_light_occluder(int p_id) const; + + void autotile_set_light_occluder(int p_id, const Ref &p_light_occluder, const Vector2 &p_coord); + Ref autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const; + const Map> &autotile_get_light_oclusion_map(int p_id) const; + + void tile_set_navigation_polygon_offset(int p_id, const Vector2 &p_offset); + Vector2 tile_get_navigation_polygon_offset(int p_id) const; + + void tile_set_navigation_polygon(int p_id, const Ref &p_navigation_polygon); + Ref tile_get_navigation_polygon(int p_id) const; + + void autotile_set_navigation_polygon(int p_id, const Ref &p_navigation_polygon, const Vector2 &p_coord); + Ref autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const; + const Map> &autotile_get_navigation_map(int p_id) const; + + void tile_set_z_index(int p_id, int p_z_index); + int tile_get_z_index(int p_id) const; + + void remove_tile(int p_id); + + bool has_tile(int p_id) const; + + bool is_tile_bound(int p_drawn_id, int p_neighbor_id); + + int find_tile_by_name(const String &p_name) const; + void get_tile_list(List *p_tiles) const; + + void clear(); + + int get_last_unused_tile_id() const; + + RTileSet(); +}; + +VARIANT_ENUM_CAST(RTileSet::AutotileBindings); +VARIANT_ENUM_CAST(RTileSet::BitmaskMode); +VARIANT_ENUM_CAST(RTileSet::TileMode); + +#endif // TILE_SET_H diff --git a/tile_set_editor_plugin.cpp b/tile_set_editor_plugin.cpp new file mode 100644 index 0000000..6d32c87 --- /dev/null +++ b/tile_set_editor_plugin.cpp @@ -0,0 +1,3668 @@ +/*************************************************************************/ +/* tile_set_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set_editor_plugin.h" + +#include "core/os/input.h" +#include "core/os/keyboard.h" +#include "editor/editor_scale.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "scene/2d/physics_body_2d.h" +#include "scene/2d/sprite.h" + +void RTileSetEditor::edit(const Ref &p_tileset) { + tileset = p_tileset; + tileset->add_change_receptor(this); + + texture_list->clear(); + texture_map.clear(); + update_texture_list(); +} + +void RTileSetEditor::_import_node(Node *p_node, Ref p_library) { + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + + if (!Object::cast_to(child)) { + if (child->get_child_count() > 0) { + _import_node(child, p_library); + } + + continue; + } + + Sprite *mi = Object::cast_to(child); + Ref texture = mi->get_texture(); + Ref normal_map = mi->get_normal_map(); + Ref material = mi->get_material(); + + if (texture.is_null()) { + continue; + } + + int id = p_library->find_tile_by_name(mi->get_name()); + if (id < 0) { + id = p_library->get_last_unused_tile_id(); + p_library->create_tile(id); + p_library->tile_set_name(id, mi->get_name()); + } + + p_library->tile_set_texture(id, texture); + p_library->tile_set_normal_map(id, normal_map); + p_library->tile_set_material(id, material); + + p_library->tile_set_modulate(id, mi->get_modulate()); + + Vector2 phys_offset; + Size2 s; + + if (mi->is_region()) { + s = mi->get_region_rect().size; + p_library->tile_set_region(id, mi->get_region_rect()); + } else { + const int frame = mi->get_frame(); + const int hframes = mi->get_hframes(); + s = texture->get_size() / Size2(hframes, mi->get_vframes()); + p_library->tile_set_region(id, Rect2(Vector2(frame % hframes, frame / hframes) * s, s)); + } + + if (mi->is_centered()) { + phys_offset += -s / 2; + } + + Vector collisions; + Ref nav_poly; + Ref occluder; + bool found_collisions = false; + + for (int j = 0; j < mi->get_child_count(); j++) { + Node *child2 = mi->get_child(j); + + if (Object::cast_to(child2)) { + nav_poly = Object::cast_to(child2)->get_navigation_polygon(); + } + + if (Object::cast_to(child2)) { + occluder = Object::cast_to(child2)->get_occluder_polygon(); + } + + if (!Object::cast_to(child2)) { + continue; + } + + found_collisions = true; + + StaticBody2D *sb = Object::cast_to(child2); + + List shapes; + sb->get_shape_owners(&shapes); + + for (List::Element *E = shapes.front(); E; E = E->next()) { + if (sb->is_shape_owner_disabled(E->get())) { + continue; + } + + Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get()); + bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get()); + + shape_transform[2] -= phys_offset; + + for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) { + Ref shape = sb->shape_owner_get_shape(E->get(), k); + RTileSet::ShapeData shape_data; + shape_data.shape = shape; + shape_data.shape_transform = shape_transform; + shape_data.one_way_collision = one_way; + collisions.push_back(shape_data); + } + } + } + + if (found_collisions) { + p_library->tile_set_shapes(id, collisions); + } + + p_library->tile_set_texture_offset(id, mi->get_offset()); + p_library->tile_set_navigation_polygon(id, nav_poly); + p_library->tile_set_light_occluder(id, occluder); + p_library->tile_set_occluder_offset(id, -phys_offset); + p_library->tile_set_navigation_polygon_offset(id, -phys_offset); + p_library->tile_set_z_index(id, mi->get_z_index()); + } +} + +void RTileSetEditor::_import_scene(Node *p_scene, Ref p_library, bool p_merge) { + if (!p_merge) { + p_library->clear(); + } + + _import_node(p_scene, p_library); +} + +void RTileSetEditor::_undo_redo_import_scene(Node *p_scene, bool p_merge) { + _import_scene(p_scene, tileset, p_merge); +} + +Error RTileSetEditor::update_library_file(Node *p_base_scene, Ref ml, bool p_merge) { + _import_scene(p_base_scene, ml, p_merge); + return OK; +} + +Variant RTileSetEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + return false; +} + +bool RTileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + Dictionary d = p_data; + + if (!d.has("type")) { + return false; + } + + if (d.has("from") && (Object *)(d["from"]) == texture_list) { + return false; + } + + if (String(d["type"]) == "resource" && d.has("resource")) { + RES r = d["resource"]; + + Ref texture = r; + + if (texture.is_valid()) { + return true; + } + } + + if (String(d["type"]) == "files") { + Vector files = d["files"]; + + if (files.size() == 0) { + return false; + } + + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + String ftype = EditorFileSystem::get_singleton()->get_file_type(file); + + if (!ClassDB::is_parent_class(ftype, "Texture")) { + return false; + } + } + + return true; + } + return false; +} + +void RTileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) { + return; + } + + Dictionary d = p_data; + + if (!d.has("type")) { + return; + } + + if (String(d["type"]) == "resource" && d.has("resource")) { + RES r = d["resource"]; + + Ref texture = r; + + if (texture.is_valid()) { + add_texture(texture); + } + + if (texture_list->get_item_count() > 0) { + update_texture_list_icon(); + texture_list->select(texture_list->get_item_count() - 1); + _on_texture_list_selected(texture_list->get_item_count() - 1); + } + } + + if (String(d["type"]) == "files") { + PoolVector files = d["files"]; + + _on_textures_added(files); + } +} + +void RTileSetEditor::_bind_methods() { + ClassDB::bind_method("_undo_redo_import_scene", &RTileSetEditor::_undo_redo_import_scene); + ClassDB::bind_method("_on_tileset_toolbar_button_pressed", &RTileSetEditor::_on_tileset_toolbar_button_pressed); + ClassDB::bind_method("_on_textures_added", &RTileSetEditor::_on_textures_added); + ClassDB::bind_method("_on_tileset_toolbar_confirm", &RTileSetEditor::_on_tileset_toolbar_confirm); + ClassDB::bind_method("_on_texture_list_selected", &RTileSetEditor::_on_texture_list_selected); + ClassDB::bind_method("_on_edit_mode_changed", &RTileSetEditor::_on_edit_mode_changed); + ClassDB::bind_method("_on_scroll_container_input", &RTileSetEditor::_on_scroll_container_input); + ClassDB::bind_method("_on_workspace_mode_changed", &RTileSetEditor::_on_workspace_mode_changed); + ClassDB::bind_method("_on_workspace_overlay_draw", &RTileSetEditor::_on_workspace_overlay_draw); + ClassDB::bind_method("_on_workspace_process", &RTileSetEditor::_on_workspace_process); + ClassDB::bind_method("_on_workspace_draw", &RTileSetEditor::_on_workspace_draw); + ClassDB::bind_method("_on_workspace_input", &RTileSetEditor::_on_workspace_input); + ClassDB::bind_method("_on_tool_clicked", &RTileSetEditor::_on_tool_clicked); + ClassDB::bind_method("_on_priority_changed", &RTileSetEditor::_on_priority_changed); + ClassDB::bind_method("_on_z_index_changed", &RTileSetEditor::_on_z_index_changed); + ClassDB::bind_method("_on_grid_snap_toggled", &RTileSetEditor::_on_grid_snap_toggled); + ClassDB::bind_method("_set_snap_step", &RTileSetEditor::_set_snap_step); + ClassDB::bind_method("_set_snap_off", &RTileSetEditor::_set_snap_off); + ClassDB::bind_method("_set_snap_sep", &RTileSetEditor::_set_snap_sep); + ClassDB::bind_method("_validate_current_tile_id", &RTileSetEditor::_validate_current_tile_id); + ClassDB::bind_method("_zoom_in", &RTileSetEditor::_zoom_in); + ClassDB::bind_method("_zoom_out", &RTileSetEditor::_zoom_out); + ClassDB::bind_method("_zoom_reset", &RTileSetEditor::_zoom_reset); + ClassDB::bind_method("_select_edited_shape_coord", &RTileSetEditor::_select_edited_shape_coord); + ClassDB::bind_method("_sort_tiles", &RTileSetEditor::_sort_tiles); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &RTileSetEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &RTileSetEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &RTileSetEditor::drop_data_fw); + + ClassDB::bind_method("edit", &RTileSetEditor::edit); + ClassDB::bind_method("add_texture", &RTileSetEditor::add_texture); + ClassDB::bind_method("remove_texture", &RTileSetEditor::remove_texture); + ClassDB::bind_method("update_texture_list_icon", &RTileSetEditor::update_texture_list_icon); + ClassDB::bind_method("update_workspace_minsize", &RTileSetEditor::update_workspace_minsize); +} + +void RTileSetEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + add_constant_override("autohide", 1); // Fixes the dragger always showing up. + } break; + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_icon(get_icon("ToolAddNode", "EditorIcons")); + tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_icon(get_icon("Remove", "EditorIcons")); + tileset_toolbar_tools->set_icon(get_icon("Tools", "EditorIcons")); + + tool_workspacemode[WORKSPACE_EDIT]->set_icon(get_icon("Edit", "EditorIcons")); + tool_workspacemode[WORKSPACE_CREATE_SINGLE]->set_icon(get_icon("AddSingleTile", "EditorIcons")); + tool_workspacemode[WORKSPACE_CREATE_AUTOTILE]->set_icon(get_icon("AddAutotile", "EditorIcons")); + tool_workspacemode[WORKSPACE_CREATE_ATLAS]->set_icon(get_icon("AddAtlasTile", "EditorIcons")); + + tools[TOOL_SELECT]->set_icon(get_icon("ToolSelect", "EditorIcons")); + tools[BITMASK_COPY]->set_icon(get_icon("Duplicate", "EditorIcons")); + tools[BITMASK_PASTE]->set_icon(get_icon("Override", "EditorIcons")); + tools[BITMASK_CLEAR]->set_icon(get_icon("Clear", "EditorIcons")); + tools[SHAPE_NEW_POLYGON]->set_icon(get_icon("CollisionPolygon2D", "EditorIcons")); + tools[SHAPE_NEW_RECTANGLE]->set_icon(get_icon("CollisionShape2D", "EditorIcons")); + tools[SELECT_PREVIOUS]->set_icon(get_icon("ArrowLeft", "EditorIcons")); + tools[SELECT_NEXT]->set_icon(get_icon("ArrowRight", "EditorIcons")); + tools[SHAPE_DELETE]->set_icon(get_icon("Remove", "EditorIcons")); + tools[SHAPE_KEEP_INSIDE_TILE]->set_icon(get_icon("Snap", "EditorIcons")); + tools[TOOL_GRID_SNAP]->set_icon(get_icon("SnapGrid", "EditorIcons")); + tools[ZOOM_OUT]->set_icon(get_icon("ZoomLess", "EditorIcons")); + tools[ZOOM_1]->set_icon(get_icon("ZoomReset", "EditorIcons")); + tools[ZOOM_IN]->set_icon(get_icon("ZoomMore", "EditorIcons")); + tools[VISIBLE_INFO]->set_icon(get_icon("InformationSign", "EditorIcons")); + _update_toggle_shape_button(); + + tool_editmode[EDITMODE_REGION]->set_icon(get_icon("RegionEdit", "EditorIcons")); + tool_editmode[EDITMODE_COLLISION]->set_icon(get_icon("StaticBody2D", "EditorIcons")); + tool_editmode[EDITMODE_OCCLUSION]->set_icon(get_icon("LightOccluder2D", "EditorIcons")); + tool_editmode[EDITMODE_NAVIGATION]->set_icon(get_icon("Navigation2D", "EditorIcons")); + tool_editmode[EDITMODE_BITMASK]->set_icon(get_icon("PackedDataContainer", "EditorIcons")); + tool_editmode[EDITMODE_PRIORITY]->set_icon(get_icon("MaterialPreviewLight1", "EditorIcons")); + tool_editmode[EDITMODE_ICON]->set_icon(get_icon("LargeTexture", "EditorIcons")); + tool_editmode[EDITMODE_Z_INDEX]->set_icon(get_icon("Sort", "EditorIcons")); + + scroll->add_style_override("bg", get_stylebox("bg", "Tree")); + } break; + } +} + +RTileSetEditor::RTileSetEditor(EditorNode *p_editor) { + editor = p_editor; + undo_redo = EditorNode::get_undo_redo(); + current_tile = -1; + + VBoxContainer *left_container = memnew(VBoxContainer); + add_child(left_container); + + texture_list = memnew(ItemList); + left_container->add_child(texture_list); + texture_list->set_v_size_flags(SIZE_EXPAND_FILL); + texture_list->set_custom_minimum_size(Size2(200, 0)); + texture_list->connect("item_selected", this, "_on_texture_list_selected"); + texture_list->set_drag_forwarding(this); + + HBoxContainer *tileset_toolbar_container = memnew(HBoxContainer); + left_container->add_child(tileset_toolbar_container); + + tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE] = memnew(ToolButton); + tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->connect("pressed", this, "_on_tileset_toolbar_button_pressed", varray(TOOL_TILESET_ADD_TEXTURE)); + tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]); + tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_tooltip(TTR("Add Texture(s) to TileSet.")); + + tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE] = memnew(ToolButton); + tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->connect("pressed", this, "_on_tileset_toolbar_button_pressed", varray(TOOL_TILESET_REMOVE_TEXTURE)); + tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]); + tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_tooltip(TTR("Remove selected Texture from TileSet.")); + + Control *toolbar_separator = memnew(Control); + toolbar_separator->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tileset_toolbar_container->add_child(toolbar_separator); + + tileset_toolbar_tools = memnew(MenuButton); + tileset_toolbar_tools->set_text(TTR("Tools")); + tileset_toolbar_tools->get_popup()->add_item(TTR("Create from Scene"), TOOL_TILESET_CREATE_SCENE); + tileset_toolbar_tools->get_popup()->add_item(TTR("Merge from Scene"), TOOL_TILESET_MERGE_SCENE); + + tileset_toolbar_tools->get_popup()->connect("id_pressed", this, "_on_tileset_toolbar_button_pressed"); + tileset_toolbar_container->add_child(tileset_toolbar_tools); + + //--------------- + VBoxContainer *right_container = memnew(VBoxContainer); + right_container->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(right_container); + + dragging_point = -1; + creating_shape = false; + snap_step = Vector2(32, 32); + snap_offset = WORKSPACE_MARGIN; + + set_custom_minimum_size(Size2(0, 150)); + + VBoxContainer *main_vb = memnew(VBoxContainer); + right_container->add_child(main_vb); + main_vb->set_v_size_flags(SIZE_EXPAND_FILL); + + HBoxContainer *tool_hb = memnew(HBoxContainer); + Ref g(memnew(ButtonGroup)); + + String workspace_label[WORKSPACE_MODE_MAX] = { + TTR("Edit"), + TTR("New Single Tile"), + TTR("New Autotile"), + TTR("New Atlas") + }; + for (int i = 0; i < (int)WORKSPACE_MODE_MAX; i++) { + tool_workspacemode[i] = memnew(Button); + tool_workspacemode[i]->set_text(workspace_label[i]); + tool_workspacemode[i]->set_toggle_mode(true); + tool_workspacemode[i]->set_button_group(g); + tool_workspacemode[i]->connect("pressed", this, "_on_workspace_mode_changed", varray(i)); + tool_hb->add_child(tool_workspacemode[i]); + } + + Control *spacer = memnew(Control); + spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tool_hb->add_child(spacer); + tool_hb->move_child(spacer, WORKSPACE_CREATE_SINGLE); + + tools[SELECT_NEXT] = memnew(ToolButton); + tool_hb->add_child(tools[SELECT_NEXT]); + tool_hb->move_child(tools[SELECT_NEXT], WORKSPACE_CREATE_SINGLE); + tools[SELECT_NEXT]->set_shortcut(ED_SHORTCUT("tileset_editor/next_shape", TTR("Next Coordinate"), KEY_PAGEDOWN)); + tools[SELECT_NEXT]->connect("pressed", this, "_on_tool_clicked", varray(SELECT_NEXT)); + tools[SELECT_NEXT]->set_tooltip(TTR("Select the next shape, subtile, or Tile.")); + tools[SELECT_PREVIOUS] = memnew(ToolButton); + tool_hb->add_child(tools[SELECT_PREVIOUS]); + tool_hb->move_child(tools[SELECT_PREVIOUS], WORKSPACE_CREATE_SINGLE); + tools[SELECT_PREVIOUS]->set_shortcut(ED_SHORTCUT("tileset_editor/previous_shape", TTR("Previous Coordinate"), KEY_PAGEUP)); + tools[SELECT_PREVIOUS]->set_tooltip(TTR("Select the previous shape, subtile, or Tile.")); + tools[SELECT_PREVIOUS]->connect("pressed", this, "_on_tool_clicked", varray(SELECT_PREVIOUS)); + + VSeparator *separator_shape_selection = memnew(VSeparator); + tool_hb->add_child(separator_shape_selection); + tool_hb->move_child(separator_shape_selection, WORKSPACE_CREATE_SINGLE); + + tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); + workspace_mode = WORKSPACE_EDIT; + + main_vb->add_child(tool_hb); + main_vb->add_child(memnew(HSeparator)); + + tool_hb = memnew(HBoxContainer); + + g = Ref(memnew(ButtonGroup)); + String label[EDITMODE_MAX] = { + TTR("Region"), + TTR("Collision"), + TTR("Occlusion"), + TTR("Navigation"), + TTR("Bitmask"), + TTR("Priority"), + TTR("Icon"), + TTR("Z Index") + }; + for (int i = 0; i < (int)EDITMODE_MAX; i++) { + tool_editmode[i] = memnew(Button); + tool_editmode[i]->set_text(label[i]); + tool_editmode[i]->set_toggle_mode(true); + tool_editmode[i]->set_button_group(g); + tool_editmode[i]->connect("pressed", this, "_on_edit_mode_changed", varray(i)); + tool_hb->add_child(tool_editmode[i]); + } + tool_editmode[EDITMODE_COLLISION]->set_pressed(true); + edit_mode = EDITMODE_COLLISION; + + tool_editmode[EDITMODE_REGION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_region", TTR("Region Mode"), KEY_1)); + tool_editmode[EDITMODE_COLLISION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_collision", TTR("Collision Mode"), KEY_2)); + tool_editmode[EDITMODE_OCCLUSION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_occlusion", TTR("Occlusion Mode"), KEY_3)); + tool_editmode[EDITMODE_NAVIGATION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_navigation", TTR("Navigation Mode"), KEY_4)); + tool_editmode[EDITMODE_BITMASK]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_bitmask", TTR("Bitmask Mode"), KEY_5)); + tool_editmode[EDITMODE_PRIORITY]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_priority", TTR("Priority Mode"), KEY_6)); + tool_editmode[EDITMODE_ICON]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_icon", TTR("Icon Mode"), KEY_7)); + tool_editmode[EDITMODE_Z_INDEX]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_z_index", TTR("Z Index Mode"), KEY_8)); + + main_vb->add_child(tool_hb); + separator_editmode = memnew(HSeparator); + main_vb->add_child(separator_editmode); + + toolbar = memnew(HBoxContainer); + Ref tg(memnew(ButtonGroup)); + + tools[TOOL_SELECT] = memnew(ToolButton); + toolbar->add_child(tools[TOOL_SELECT]); + tools[TOOL_SELECT]->set_toggle_mode(true); + tools[TOOL_SELECT]->set_button_group(tg); + tools[TOOL_SELECT]->set_pressed(true); + tools[TOOL_SELECT]->connect("pressed", this, "_on_tool_clicked", varray(TOOL_SELECT)); + + separator_bitmask = memnew(VSeparator); + toolbar->add_child(separator_bitmask); + tools[BITMASK_COPY] = memnew(ToolButton); + tools[BITMASK_COPY]->set_tooltip(TTR("Copy bitmask.")); + tools[BITMASK_COPY]->connect("pressed", this, "_on_tool_clicked", varray(BITMASK_COPY)); + toolbar->add_child(tools[BITMASK_COPY]); + tools[BITMASK_PASTE] = memnew(ToolButton); + tools[BITMASK_PASTE]->set_tooltip(TTR("Paste bitmask.")); + tools[BITMASK_PASTE]->connect("pressed", this, "_on_tool_clicked", varray(BITMASK_PASTE)); + toolbar->add_child(tools[BITMASK_PASTE]); + tools[BITMASK_CLEAR] = memnew(ToolButton); + tools[BITMASK_CLEAR]->set_tooltip(TTR("Erase bitmask.")); + tools[BITMASK_CLEAR]->connect("pressed", this, "_on_tool_clicked", varray(BITMASK_CLEAR)); + toolbar->add_child(tools[BITMASK_CLEAR]); + + tools[SHAPE_NEW_RECTANGLE] = memnew(ToolButton); + toolbar->add_child(tools[SHAPE_NEW_RECTANGLE]); + tools[SHAPE_NEW_RECTANGLE]->set_toggle_mode(true); + tools[SHAPE_NEW_RECTANGLE]->set_button_group(tg); + tools[SHAPE_NEW_RECTANGLE]->set_tooltip(TTR("Create a new rectangle.")); + tools[SHAPE_NEW_RECTANGLE]->connect("pressed", this, "_on_tool_clicked", varray(SHAPE_NEW_RECTANGLE)); + tools[SHAPE_NEW_RECTANGLE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_rectangle", TTR("New Rectangle"), KEY_MASK_SHIFT | KEY_R)); + + tools[SHAPE_NEW_POLYGON] = memnew(ToolButton); + toolbar->add_child(tools[SHAPE_NEW_POLYGON]); + tools[SHAPE_NEW_POLYGON]->set_toggle_mode(true); + tools[SHAPE_NEW_POLYGON]->set_button_group(tg); + tools[SHAPE_NEW_POLYGON]->set_tooltip(TTR("Create a new polygon.")); + tools[SHAPE_NEW_POLYGON]->connect("pressed", this, "_on_tool_clicked", varray(SHAPE_NEW_POLYGON)); + tools[SHAPE_NEW_POLYGON]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_polygon", TTR("New Polygon"), KEY_MASK_SHIFT | KEY_P)); + + separator_shape_toggle = memnew(VSeparator); + toolbar->add_child(separator_shape_toggle); + tools[SHAPE_TOGGLE_TYPE] = memnew(ToolButton); + tools[SHAPE_TOGGLE_TYPE]->connect("pressed", this, "_on_tool_clicked", varray(SHAPE_TOGGLE_TYPE)); + toolbar->add_child(tools[SHAPE_TOGGLE_TYPE]); + + separator_delete = memnew(VSeparator); + toolbar->add_child(separator_delete); + tools[SHAPE_DELETE] = memnew(ToolButton); + tools[SHAPE_DELETE]->connect("pressed", this, "_on_tool_clicked", varray(SHAPE_DELETE)); + tools[SHAPE_DELETE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_delete", TTR("Delete Selected Shape"), KEY_MASK_SHIFT | KEY_BACKSPACE)); + toolbar->add_child(tools[SHAPE_DELETE]); + + spin_priority = memnew(SpinBox); + spin_priority->set_min(1); + spin_priority->set_max(255); + spin_priority->set_step(1); + spin_priority->set_custom_minimum_size(Size2(100, 0)); + spin_priority->connect("value_changed", this, "_on_priority_changed"); + spin_priority->hide(); + toolbar->add_child(spin_priority); + + spin_z_index = memnew(SpinBox); + spin_z_index->set_min(VS::CANVAS_ITEM_Z_MIN); + spin_z_index->set_max(VS::CANVAS_ITEM_Z_MAX); + spin_z_index->set_step(1); + spin_z_index->set_custom_minimum_size(Size2(100, 0)); + spin_z_index->connect("value_changed", this, "_on_z_index_changed"); + spin_z_index->hide(); + toolbar->add_child(spin_z_index); + + separator_grid = memnew(VSeparator); + toolbar->add_child(separator_grid); + tools[SHAPE_KEEP_INSIDE_TILE] = memnew(ToolButton); + tools[SHAPE_KEEP_INSIDE_TILE]->set_toggle_mode(true); + tools[SHAPE_KEEP_INSIDE_TILE]->set_pressed(true); + tools[SHAPE_KEEP_INSIDE_TILE]->set_tooltip(TTR("Keep polygon inside region Rect.")); + toolbar->add_child(tools[SHAPE_KEEP_INSIDE_TILE]); + tools[TOOL_GRID_SNAP] = memnew(ToolButton); + tools[TOOL_GRID_SNAP]->set_toggle_mode(true); + tools[TOOL_GRID_SNAP]->set_tooltip(TTR("Enable snap and show grid (configurable via the Inspector).")); + tools[TOOL_GRID_SNAP]->connect("toggled", this, "_on_grid_snap_toggled"); + toolbar->add_child(tools[TOOL_GRID_SNAP]); + + Control *separator = memnew(Control); + separator->set_h_size_flags(SIZE_EXPAND_FILL); + toolbar->add_child(separator); + + tools[ZOOM_OUT] = memnew(ToolButton); + tools[ZOOM_OUT]->connect("pressed", this, "_zoom_out"); + toolbar->add_child(tools[ZOOM_OUT]); + tools[ZOOM_OUT]->set_tooltip(TTR("Zoom Out")); + tools[ZOOM_1] = memnew(ToolButton); + tools[ZOOM_1]->connect("pressed", this, "_zoom_reset"); + toolbar->add_child(tools[ZOOM_1]); + tools[ZOOM_1]->set_tooltip(TTR("Zoom Reset")); + tools[ZOOM_IN] = memnew(ToolButton); + tools[ZOOM_IN]->connect("pressed", this, "_zoom_in"); + toolbar->add_child(tools[ZOOM_IN]); + tools[ZOOM_IN]->set_tooltip(TTR("Zoom In")); + + tools[VISIBLE_INFO] = memnew(ToolButton); + tools[VISIBLE_INFO]->set_toggle_mode(true); + tools[VISIBLE_INFO]->set_tooltip(TTR("Display Tile Names (Hold Alt Key)")); + toolbar->add_child(tools[VISIBLE_INFO]); + + main_vb->add_child(toolbar); + + scroll = memnew(ScrollContainer); + main_vb->add_child(scroll); + scroll->set_v_size_flags(SIZE_EXPAND_FILL); + scroll->connect("gui_input", this, "_on_scroll_container_input"); + scroll->set_clip_contents(true); + + empty_message = memnew(Label); + empty_message->set_text(TTR("Add or select a texture on the left panel to edit the tiles bound to it.")); + empty_message->set_valign(Label::VALIGN_CENTER); + empty_message->set_align(Label::ALIGN_CENTER); + empty_message->set_autowrap(true); + empty_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + empty_message->set_v_size_flags(SIZE_EXPAND_FILL); + main_vb->add_child(empty_message); + + workspace_container = memnew(Control); + scroll->add_child(workspace_container); + + workspace_overlay = memnew(Control); + workspace_overlay->connect("draw", this, "_on_workspace_overlay_draw"); + workspace_container->add_child(workspace_overlay); + + workspace = memnew(Control); + workspace->set_focus_mode(FOCUS_ALL); + workspace->connect("draw", this, "_on_workspace_draw"); + workspace->connect("gui_input", this, "_on_workspace_input"); + workspace->set_draw_behind_parent(true); + workspace_overlay->add_child(workspace); + + preview = memnew(Sprite); + workspace->add_child(preview); + preview->set_centered(false); + preview->set_draw_behind_parent(true); + preview->set_position(WORKSPACE_MARGIN); + + //--------------- + cd = memnew(ConfirmationDialog); + add_child(cd); + cd->connect("confirmed", this, "_on_tileset_toolbar_confirm"); + + //--------------- + err_dialog = memnew(AcceptDialog); + add_child(err_dialog); + + //--------------- + texture_dialog = memnew(EditorFileDialog); + texture_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); + texture_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILES); + texture_dialog->clear_filters(); + List extensions; + + ResourceLoader::get_recognized_extensions_for_type("Texture", &extensions); + for (List::Element *E = extensions.front(); E; E = E->next()) { + texture_dialog->add_filter("*." + E->get() + " ; " + E->get().to_upper()); + } + add_child(texture_dialog); + texture_dialog->connect("files_selected", this, "_on_textures_added"); + + //--------------- + helper = memnew(RTilesetEditorContext(this)); + tile_names_visible = false; + + // Config scale. + max_scale = 16.0f; + min_scale = 0.01f; + scale_ratio = 1.2f; +} + +RTileSetEditor::~RTileSetEditor() { + if (helper) { + memdelete(helper); + } +} + +void RTileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) { + option = p_index; + switch (option) { + case TOOL_TILESET_ADD_TEXTURE: { + texture_dialog->popup_centered_ratio(); + } break; + case TOOL_TILESET_REMOVE_TEXTURE: { + if (get_current_texture().is_valid()) { + cd->set_text(TTR("Remove selected texture? This will remove all tiles which use it.")); + cd->popup_centered(Size2(300, 60)); + } else { + err_dialog->set_text(TTR("You haven't selected a texture to remove.")); + err_dialog->popup_centered(Size2(300, 60)); + } + } break; + case TOOL_TILESET_CREATE_SCENE: { + cd->set_text(TTR("Create from scene? This will overwrite all current tiles.")); + cd->popup_centered(Size2(300, 60)); + } break; + case TOOL_TILESET_MERGE_SCENE: { + cd->set_text(TTR("Merge from scene?")); + cd->popup_centered(Size2(300, 60)); + } break; + } +} + +void RTileSetEditor::_on_tileset_toolbar_confirm() { + switch (option) { + case TOOL_TILESET_REMOVE_TEXTURE: { + String current_texture_path = get_current_texture()->get_path(); + List ids; + tileset->get_tile_list(&ids); + + undo_redo->create_action(TTR("Remove Texture")); + for (List::Element *E = ids.front(); E; E = E->next()) { + if (tileset->tile_get_texture(E->get())->get_path() == current_texture_path) { + undo_redo->add_do_method(tileset.ptr(), "remove_tile", E->get()); + _undo_tile_removal(E->get()); + } + } + undo_redo->add_do_method(this, "remove_texture", get_current_texture()); + undo_redo->add_undo_method(this, "add_texture", get_current_texture()); + undo_redo->add_undo_method(this, "update_texture_list_icon"); + undo_redo->commit_action(); + } break; + case TOOL_TILESET_MERGE_SCENE: + case TOOL_TILESET_CREATE_SCENE: { + EditorNode *en = editor; + Node *scene = en->get_edited_scene(); + if (!scene) { + break; + } + + List ids; + tileset->get_tile_list(&ids); + + undo_redo->create_action(TTR(option == TOOL_TILESET_MERGE_SCENE ? "Merge Tileset from Scene" : "Create Tileset from Scene")); + undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE); + undo_redo->add_undo_method(tileset.ptr(), "clear"); + for (List::Element *E = ids.front(); E; E = E->next()) { + _undo_tile_removal(E->get()); + } + undo_redo->add_do_method(this, "edit", tileset); + undo_redo->add_undo_method(this, "edit", tileset); + undo_redo->commit_action(); + } break; + } +} + +void RTileSetEditor::_on_texture_list_selected(int p_index) { + if (get_current_texture().is_valid()) { + current_item_index = p_index; + preview->set_texture(get_current_texture()); + update_workspace_tile_mode(); + update_workspace_minsize(); + } else { + current_item_index = -1; + preview->set_texture(nullptr); + workspace->set_custom_minimum_size(Size2i()); + update_workspace_tile_mode(); + } + + set_current_tile(-1); + workspace->update(); +} + +void RTileSetEditor::_on_textures_added(const PoolStringArray &p_paths) { + int invalid_count = 0; + for (int i = 0; i < p_paths.size(); i++) { + Ref t = Ref(ResourceLoader::load(p_paths[i])); + + ERR_CONTINUE_MSG(!t.is_valid(), "'" + p_paths[i] + "' is not a valid texture."); + + if (texture_map.has(t->get_path())) { + invalid_count++; + } else { + add_texture(t); + } + } + + if (texture_list->get_item_count() > 0) { + update_texture_list_icon(); + texture_list->select(texture_list->get_item_count() - 1); + _on_texture_list_selected(texture_list->get_item_count() - 1); + } + + if (invalid_count > 0) { + err_dialog->set_text(vformat(TTR("%s file(s) were not added because was already on the list."), String::num(invalid_count, 0))); + err_dialog->popup_centered(Size2(300, 60)); + } +} + +void RTileSetEditor::_on_edit_mode_changed(int p_edit_mode) { + draw_handles = false; + creating_shape = false; + edit_mode = (EditMode)p_edit_mode; + switch (edit_mode) { + case EDITMODE_REGION: { + tools[TOOL_SELECT]->show(); + + separator_bitmask->hide(); + tools[BITMASK_COPY]->hide(); + tools[BITMASK_PASTE]->hide(); + tools[BITMASK_CLEAR]->hide(); + tools[SHAPE_NEW_POLYGON]->hide(); + tools[SHAPE_NEW_RECTANGLE]->hide(); + + if (workspace_mode == WORKSPACE_EDIT) { + separator_delete->show(); + tools[SHAPE_DELETE]->show(); + } else { + separator_delete->hide(); + tools[SHAPE_DELETE]->hide(); + } + + separator_grid->show(); + tools[SHAPE_KEEP_INSIDE_TILE]->hide(); + tools[TOOL_GRID_SNAP]->show(); + + tools[TOOL_SELECT]->set_pressed(true); + tools[TOOL_SELECT]->set_tooltip(TTR("Drag handles to edit Rect.\nClick on another Tile to edit it.")); + tools[SHAPE_DELETE]->set_tooltip(TTR("Delete selected Rect.")); + spin_priority->hide(); + spin_z_index->hide(); + } break; + case EDITMODE_COLLISION: + case EDITMODE_OCCLUSION: + case EDITMODE_NAVIGATION: { + tools[TOOL_SELECT]->show(); + + separator_bitmask->hide(); + tools[BITMASK_COPY]->hide(); + tools[BITMASK_PASTE]->hide(); + tools[BITMASK_CLEAR]->hide(); + tools[SHAPE_NEW_POLYGON]->show(); + tools[SHAPE_NEW_RECTANGLE]->show(); + + separator_delete->show(); + tools[SHAPE_DELETE]->show(); + + separator_grid->show(); + tools[SHAPE_KEEP_INSIDE_TILE]->show(); + tools[TOOL_GRID_SNAP]->show(); + + tools[TOOL_SELECT]->set_tooltip(TTR("Select current edited sub-tile.\nClick on another Tile to edit it.")); + tools[SHAPE_DELETE]->set_tooltip(TTR("Delete polygon.")); + spin_priority->hide(); + spin_z_index->hide(); + + _select_edited_shape_coord(); + } break; + case EDITMODE_BITMASK: { + tools[TOOL_SELECT]->show(); + + separator_bitmask->show(); + tools[BITMASK_COPY]->show(); + tools[BITMASK_PASTE]->show(); + tools[BITMASK_CLEAR]->show(); + tools[SHAPE_NEW_POLYGON]->hide(); + tools[SHAPE_NEW_RECTANGLE]->hide(); + + separator_delete->hide(); + tools[SHAPE_DELETE]->hide(); + + tools[SHAPE_KEEP_INSIDE_TILE]->hide(); + + tools[TOOL_SELECT]->set_pressed(true); + tools[TOOL_SELECT]->set_tooltip(TTR("LMB: Set bit on.\nRMB: Set bit off.\nShift+LMB: Set wildcard bit.\nClick on another Tile to edit it.")); + spin_priority->hide(); + } break; + case EDITMODE_Z_INDEX: + case EDITMODE_PRIORITY: + case EDITMODE_ICON: { + tools[TOOL_SELECT]->show(); + + separator_bitmask->hide(); + tools[BITMASK_COPY]->hide(); + tools[BITMASK_PASTE]->hide(); + tools[BITMASK_CLEAR]->hide(); + tools[SHAPE_NEW_POLYGON]->hide(); + tools[SHAPE_NEW_RECTANGLE]->hide(); + + separator_delete->hide(); + tools[SHAPE_DELETE]->hide(); + + separator_grid->show(); + tools[SHAPE_KEEP_INSIDE_TILE]->hide(); + tools[TOOL_GRID_SNAP]->show(); + + if (edit_mode == EDITMODE_ICON) { + tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to use as icon, this will be also used on invalid autotile bindings.\nClick on another Tile to edit it.")); + spin_priority->hide(); + spin_z_index->hide(); + } else if (edit_mode == EDITMODE_PRIORITY) { + tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its priority.\nClick on another Tile to edit it.")); + spin_priority->show(); + spin_z_index->hide(); + } else { + tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its z index.\nClick on another Tile to edit it.")); + spin_priority->hide(); + spin_z_index->show(); + } + } break; + default: { + } + } + _update_toggle_shape_button(); + workspace->update(); +} + +void RTileSetEditor::_on_workspace_mode_changed(int p_workspace_mode) { + workspace_mode = (WorkspaceMode)p_workspace_mode; + if (p_workspace_mode == WORKSPACE_EDIT) { + update_workspace_tile_mode(); + } else { + for (int i = 0; i < EDITMODE_MAX; i++) { + tool_editmode[i]->hide(); + } + tool_editmode[EDITMODE_REGION]->show(); + tool_editmode[EDITMODE_REGION]->set_pressed(true); + _on_edit_mode_changed(EDITMODE_REGION); + separator_editmode->show(); + } +} + +void RTileSetEditor::_on_workspace_draw() { + if (tileset.is_null() || !get_current_texture().is_valid()) { + return; + } + + const Color COLOR_AUTOTILE = Color(0.3, 0.6, 1); + const Color COLOR_SINGLE = Color(1, 1, 0.3); + const Color COLOR_ATLAS = Color(0.8, 0.8, 0.8); + const Color COLOR_SUBDIVISION = Color(0.3, 0.7, 0.6); + + draw_handles = false; + + draw_highlight_current_tile(); + + draw_grid_snap(); + if (get_current_tile() >= 0) { + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + Rect2i region = tileset->tile_get_region(get_current_tile()); + + switch (edit_mode) { + case EDITMODE_ICON: { + Vector2 coord = tileset->autotile_get_icon_coordinate(get_current_tile()); + draw_highlight_subtile(coord); + } break; + case EDITMODE_BITMASK: { + Color c(1, 0, 0, 0.5); + Color ci(0.3, 0.6, 1, 0.5); + for (int x = 0; x < region.size.x / (spacing + size.x); x++) { + for (int y = 0; y < region.size.y / (spacing + size.y); y++) { + Vector2 coord(x, y); + Point2 anchor(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); + anchor += WORKSPACE_MARGIN; + anchor += region.position; + uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord); + if (tileset->autotile_get_bitmask_mode(get_current_tile()) == RTileSet::BITMASK_2X2) { + if (mask & RTileSet::BIND_IGNORE_TOPLEFT) { + workspace->draw_rect(Rect2(anchor, size / 4), ci); + workspace->draw_rect(Rect2(anchor + size / 4, size / 4), ci); + } else if (mask & RTileSet::BIND_TOPLEFT) { + workspace->draw_rect(Rect2(anchor, size / 2), c); + } + if (mask & RTileSet::BIND_IGNORE_TOPRIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 4), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x * 3 / 4, size.y / 4), size / 4), ci); + } else if (mask & RTileSet::BIND_TOPRIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 2), c); + } + if (mask & RTileSet::BIND_IGNORE_BOTTOMLEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 4), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 4, size.y * 3 / 4), size / 4), ci); + } else if (mask & RTileSet::BIND_BOTTOMLEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 2), c); + } + if (mask & RTileSet::BIND_IGNORE_BOTTOMRIGHT) { + workspace->draw_rect(Rect2(anchor + size / 2, size / 4), ci); + workspace->draw_rect(Rect2(anchor + size * 3 / 4, size / 4), ci); + } else if (mask & RTileSet::BIND_BOTTOMRIGHT) { + workspace->draw_rect(Rect2(anchor + size / 2, size / 2), c); + } + } else { + if (mask & RTileSet::BIND_IGNORE_TOPLEFT) { + workspace->draw_rect(Rect2(anchor, size / 6), ci); + workspace->draw_rect(Rect2(anchor + size / 6, size / 6), ci); + } else if (mask & RTileSet::BIND_TOPLEFT) { + workspace->draw_rect(Rect2(anchor, size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_TOP) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y / 6), size / 6), ci); + } else if (mask & RTileSet::BIND_TOP) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_TOPRIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, 0), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 6), size / 6), ci); + } else if (mask & RTileSet::BIND_TOPRIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, 0), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_LEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y / 2), size / 6), ci); + } else if (mask & RTileSet::BIND_LEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_CENTER) { + workspace->draw_rect(Rect2(anchor + size / 3, size / 6), ci); + workspace->draw_rect(Rect2(anchor + size / 2, size / 6), ci); + } else if (mask & RTileSet::BIND_CENTER) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y / 3), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_RIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, size.y / 3), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 2), size / 6), ci); + } else if (mask & RTileSet::BIND_RIGHT) { + workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, size.y / 3), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_BOTTOMLEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, size.y * 4 / 6), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y * 5 / 6), size / 6), ci); + } else if (mask & RTileSet::BIND_BOTTOMLEFT) { + workspace->draw_rect(Rect2(anchor + Vector2(0, (size.y / 3) * 2), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_BOTTOM) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y * 4 / 6), size / 6), ci); + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y * 5 / 6), size / 6), ci); + } else if (mask & RTileSet::BIND_BOTTOM) { + workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, (size.y / 3) * 2), size / 3), c); + } + if (mask & RTileSet::BIND_IGNORE_BOTTOMRIGHT) { + workspace->draw_rect(Rect2(anchor + size * 4 / 6, size / 6), ci); + workspace->draw_rect(Rect2(anchor + size * 5 / 6, size / 6), ci); + } else if (mask & RTileSet::BIND_BOTTOMRIGHT) { + workspace->draw_rect(Rect2(anchor + (size / 3) * 2, size / 3), c); + } + } + } + } + } break; + case EDITMODE_COLLISION: + case EDITMODE_OCCLUSION: + case EDITMODE_NAVIGATION: { + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + draw_highlight_subtile(edited_shape_coord); + } + draw_polygon_shapes(); + draw_grid_snap(); + } break; + case EDITMODE_PRIORITY: { + spin_priority->set_value(tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)); + uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), edited_shape_coord); + Vector queue_others; + int total = 0; + for (Map::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { + if (E->value() == mask) { + total += tileset->autotile_get_subtile_priority(get_current_tile(), E->key()); + if (E->key() != edited_shape_coord) { + queue_others.push_back(E->key()); + } + } + } + spin_priority->set_suffix(" / " + String::num(total, 0)); + draw_highlight_subtile(edited_shape_coord, queue_others); + } break; + case EDITMODE_Z_INDEX: { + spin_z_index->set_value(tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)); + draw_highlight_subtile(edited_shape_coord); + } break; + default: { + } + } + } + + String current_texture_path = get_current_texture()->get_path(); + List tiles; + tileset->get_tile_list(&tiles); + for (List::Element *E = tiles.front(); E; E = E->next()) { + int t_id = E->get(); + if (tileset->tile_get_texture(t_id)->get_path() == current_texture_path && (t_id != get_current_tile() || edit_mode != EDITMODE_REGION || workspace_mode != WORKSPACE_EDIT)) { + Rect2i region = tileset->tile_get_region(t_id); + region.position += WORKSPACE_MARGIN; + Color c; + if (tileset->tile_get_tile_mode(t_id) == RTileSet::SINGLE_TILE) { + c = COLOR_SINGLE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::AUTO_TILE) { + c = COLOR_AUTOTILE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::ATLAS_TILE) { + c = COLOR_ATLAS; + } + draw_tile_subdivision(t_id, COLOR_SUBDIVISION); + workspace->draw_rect(region, c, false); + } + } + + if (edit_mode == EDITMODE_REGION) { + if (workspace_mode != WORKSPACE_EDIT) { + Rect2i region = edited_region; + Color c; + if (workspace_mode == WORKSPACE_CREATE_SINGLE) { + c = COLOR_SINGLE; + } else if (workspace_mode == WORKSPACE_CREATE_AUTOTILE) { + c = COLOR_AUTOTILE; + } else if (workspace_mode == WORKSPACE_CREATE_ATLAS) { + c = COLOR_ATLAS; + } + workspace->draw_rect(region, c, false); + draw_edited_region_subdivision(); + } else { + int t_id = get_current_tile(); + if (t_id < 0) { + return; + } + + Rect2i region; + if (draw_edited_region) { + region = edited_region; + } else { + region = tileset->tile_get_region(t_id); + region.position += WORKSPACE_MARGIN; + } + + if (draw_edited_region) { + draw_edited_region_subdivision(); + } else { + draw_tile_subdivision(t_id, COLOR_SUBDIVISION); + } + + Color c; + if (tileset->tile_get_tile_mode(t_id) == RTileSet::SINGLE_TILE) { + c = COLOR_SINGLE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::AUTO_TILE) { + c = COLOR_AUTOTILE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::ATLAS_TILE) { + c = COLOR_ATLAS; + } + workspace->draw_rect(region, c, false); + } + } + + workspace_overlay->update(); +} + +void RTileSetEditor::_on_workspace_process() { + if (Input::get_singleton()->is_key_pressed(KEY_ALT) || tools[VISIBLE_INFO]->is_pressed()) { + if (!tile_names_visible) { + tile_names_visible = true; + workspace_overlay->update(); + } + } else if (tile_names_visible) { + tile_names_visible = false; + workspace_overlay->update(); + } +} + +void RTileSetEditor::_on_workspace_overlay_draw() { + if (!tileset.is_valid() || !get_current_texture().is_valid()) { + return; + } + + const Color COLOR_AUTOTILE = Color(0.266373, 0.565288, 0.988281); + const Color COLOR_SINGLE = Color(0.988281, 0.909323, 0.266373); + const Color COLOR_ATLAS = Color(0.78653, 0.812835, 0.832031); + + if (tile_names_visible) { + String current_texture_path = get_current_texture()->get_path(); + List tiles; + tileset->get_tile_list(&tiles); + for (List::Element *E = tiles.front(); E; E = E->next()) { + int t_id = E->get(); + if (tileset->tile_get_texture(t_id)->get_path() != current_texture_path) { + continue; + } + + Rect2 region = tileset->tile_get_region(t_id); + region.position += WORKSPACE_MARGIN; + region.position *= workspace->get_scale().x; + Color c; + if (tileset->tile_get_tile_mode(t_id) == RTileSet::SINGLE_TILE) { + c = COLOR_SINGLE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::AUTO_TILE) { + c = COLOR_AUTOTILE; + } else if (tileset->tile_get_tile_mode(t_id) == RTileSet::ATLAS_TILE) { + c = COLOR_ATLAS; + } + String tile_id_name = String::num(t_id, 0) + ": " + tileset->tile_get_name(t_id); + Ref font = get_font("font", "Label"); + region.set_size(font->get_string_size(tile_id_name)); + workspace_overlay->draw_rect(region, c); + region.position.y += region.size.y - 2; + c = Color(0.1, 0.1, 0.1); + workspace_overlay->draw_string(font, region.position, tile_id_name, c); + } + } + + int t_id = get_current_tile(); + if (t_id < 0) { + return; + } + + Ref handle = get_icon("EditorHandle", "EditorIcons"); + if (draw_handles) { + for (int i = 0; i < current_shape.size(); i++) { + workspace_overlay->draw_texture(handle, current_shape[i] * workspace->get_scale().x - handle->get_size() * 0.5); + } + } +} + +int RTileSetEditor::get_grabbed_point(const Vector2 &p_mouse_pos, real_t p_grab_threshold) { + Transform2D xform = workspace->get_transform(); + + int grabbed_point = -1; + real_t min_distance = 1e10; + + for (int i = 0; i < current_shape.size(); i++) { + const real_t distance = xform.xform(current_shape[i]).distance_to(xform.xform(p_mouse_pos)); + if (distance < p_grab_threshold && distance < min_distance) { + min_distance = distance; + grabbed_point = i; + } + } + + return grabbed_point; +} + +bool RTileSetEditor::is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold) { + Transform2D xform = workspace->get_transform(); + + const real_t distance = xform.xform(current_shape[0]).distance_to(xform.xform(p_pos)); + + return distance < p_grab_threshold; +} + +void RTileSetEditor::_on_scroll_container_input(const Ref &p_event) { + const Ref mb = p_event; + + if (mb.is_valid()) { + // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer + // to allow performing this action anywhere, even if the cursor isn't + // hovering the texture in the workspace. + if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + _zoom_in(); + // Don't scroll up after zooming in. + accept_event(); + } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + _zoom_out(); + // Don't scroll down after zooming out. + accept_event(); + } + } +} + +void RTileSetEditor::_on_workspace_input(const Ref &p_ie) { + if (tileset.is_null() || !get_current_texture().is_valid()) { + return; + } + + static bool dragging; + static bool erasing; + static bool alternative; + draw_edited_region = false; + + Rect2 current_tile_region = Rect2(); + if (get_current_tile() >= 0) { + current_tile_region = tileset->tile_get_region(get_current_tile()); + } + current_tile_region.position += WORKSPACE_MARGIN; + + const Ref mb = p_ie; + const Ref mm = p_ie; + + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && !creating_shape) { + if (!current_tile_region.has_point(mb->get_position())) { + String current_texture_path = get_current_texture()->get_path(); + List tiles; + tileset->get_tile_list(&tiles); + for (List::Element *E = tiles.front(); E; E = E->next()) { + int t_id = E->get(); + if (tileset->tile_get_texture(t_id)->get_path() == current_texture_path) { + Rect2 r = tileset->tile_get_region(t_id); + r.position += WORKSPACE_MARGIN; + if (r.has_point(mb->get_position())) { + set_current_tile(t_id); + workspace->update(); + workspace_overlay->update(); + return; + } + } + } + } + } + } + // Drag Middle Mouse + if (mm.is_valid()) { + if (mm->get_button_mask() & BUTTON_MASK_MIDDLE) { + Vector2 dragged(mm->get_relative().x, mm->get_relative().y); + scroll->set_h_scroll(scroll->get_h_scroll() - dragged.x * workspace->get_scale().x); + scroll->set_v_scroll(scroll->get_v_scroll() - dragged.y * workspace->get_scale().x); + } + } + + if (edit_mode == EDITMODE_REGION) { + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (get_current_tile() >= 0 || workspace_mode != WORKSPACE_EDIT) { + dragging = true; + region_from = mb->get_position(); + edited_region = Rect2(region_from, Size2()); + workspace->update(); + workspace_overlay->update(); + return; + } + } else if (dragging && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + dragging = false; + edited_region = Rect2(); + workspace->update(); + workspace_overlay->update(); + return; + } else if (dragging && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + dragging = false; + update_edited_region(mb->get_position()); + edited_region.position -= WORKSPACE_MARGIN; + if (!edited_region.has_no_area()) { + if (get_current_tile() >= 0 && workspace_mode == WORKSPACE_EDIT) { + undo_redo->create_action(TTR("Set Tile Region")); + undo_redo->add_do_method(tileset.ptr(), "tile_set_region", get_current_tile(), edited_region); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", get_current_tile(), tileset->tile_get_region(get_current_tile())); + + Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2; + Size2 workspace_minsize = workspace->get_custom_minimum_size(); + // If the new region is bigger, just directly change the workspace size to avoid checking all other tiles. + if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) { + Size2 max_workspace_size = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y)); + undo_redo->add_do_method(workspace, "set_custom_minimum_size", max_workspace_size); + undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize); + undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", max_workspace_size); + undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize); + undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", max_workspace_size); + undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize); + } else if (workspace_minsize.x > get_current_texture()->get_size().x + WORKSPACE_MARGIN.x * 2 || workspace_minsize.y > get_current_texture()->get_size().y + WORKSPACE_MARGIN.y * 2) { + undo_redo->add_do_method(this, "update_workspace_minsize"); + undo_redo->add_undo_method(this, "update_workspace_minsize"); + } + + edited_region = Rect2(); + + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->add_do_method(workspace_overlay, "update"); + undo_redo->add_undo_method(workspace_overlay, "update"); + undo_redo->commit_action(); + } else { + int t_id = tileset->get_last_unused_tile_id(); + undo_redo->create_action(TTR("Create Tile")); + undo_redo->add_do_method(tileset.ptr(), "create_tile", t_id); + undo_redo->add_undo_method(tileset.ptr(), "remove_tile", t_id); + undo_redo->add_undo_method(this, "_validate_current_tile_id"); + undo_redo->add_do_method(tileset.ptr(), "tile_set_texture", t_id, get_current_texture()); + undo_redo->add_do_method(tileset.ptr(), "tile_set_region", t_id, edited_region); + undo_redo->add_do_method(tileset.ptr(), "tile_set_name", t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0)); + if (workspace_mode != WORKSPACE_CREATE_SINGLE) { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_size", t_id, snap_step); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_spacing", t_id, snap_separation.x); + undo_redo->add_do_method(tileset.ptr(), "tile_set_tile_mode", t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? RTileSet::AUTO_TILE : RTileSet::ATLAS_TILE); + } + + tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); + tool_editmode[EDITMODE_COLLISION]->set_pressed(true); + edit_mode = EDITMODE_COLLISION; + + Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2; + Size2 workspace_minsize = workspace->get_custom_minimum_size(); + if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) { + Size2 new_workspace_minsize = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y)); + undo_redo->add_do_method(workspace, "set_custom_minimum_size", new_workspace_minsize); + undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize); + undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", new_workspace_minsize); + undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize); + undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", new_workspace_minsize); + undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize); + } + + edited_region = Rect2(); + + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->add_do_method(workspace_overlay, "update"); + undo_redo->add_undo_method(workspace_overlay, "update"); + undo_redo->commit_action(); + + set_current_tile(t_id); + _on_workspace_mode_changed(WORKSPACE_EDIT); + } + } else { + edited_region = Rect2(); + workspace->update(); + workspace_overlay->update(); + } + return; + } + } else if (mm.is_valid()) { + if (dragging) { + update_edited_region(mm->get_position()); + draw_edited_region = true; + workspace->update(); + workspace_overlay->update(); + return; + } + } + } + + if (workspace_mode == WORKSPACE_EDIT) { + if (get_current_tile() >= 0) { + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + switch (edit_mode) { + case EDITMODE_ICON: { + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && current_tile_region.has_point(mb->get_position())) { + Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); + undo_redo->create_action(TTR("Set Tile Icon")); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), coord); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), tileset->autotile_get_icon_coordinate(get_current_tile())); + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); + } + } + } break; + case EDITMODE_BITMASK: { + if (mb.is_valid()) { + if (mb->is_pressed()) { + if (dragging) { + return; + } + if ((mb->get_button_index() == BUTTON_RIGHT || mb->get_button_index() == BUTTON_LEFT) && current_tile_region.has_point(mb->get_position())) { + dragging = true; + erasing = (mb->get_button_index() == BUTTON_RIGHT); + alternative = Input::get_singleton()->is_key_pressed(KEY_SHIFT); + Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); + Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); + pos = mb->get_position() - (pos + current_tile_region.position); + uint32_t bit = 0; + if (tileset->autotile_get_bitmask_mode(get_current_tile()) == RTileSet::BITMASK_2X2) { + if (pos.x < size.x / 2) { + if (pos.y < size.y / 2) { + bit = RTileSet::BIND_TOPLEFT; + } else { + bit = RTileSet::BIND_BOTTOMLEFT; + } + } else { + if (pos.y < size.y / 2) { + bit = RTileSet::BIND_TOPRIGHT; + } else { + bit = RTileSet::BIND_BOTTOMRIGHT; + } + } + } else { + if (pos.x < size.x / 3) { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOPLEFT; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOMLEFT; + } else { + bit = RTileSet::BIND_LEFT; + } + } else if (pos.x > (size.x / 3) * 2) { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOPRIGHT; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOMRIGHT; + } else { + bit = RTileSet::BIND_RIGHT; + } + } else { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOP; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOM; + } else { + bit = RTileSet::BIND_CENTER; + } + } + } + + uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord); + uint32_t new_mask = old_mask; + if (alternative) { + new_mask &= ~bit; + new_mask |= (bit << 16); + } else if (erasing) { + new_mask &= ~bit; + new_mask &= ~(bit << 16); + } else { + new_mask |= bit; + new_mask &= ~(bit << 16); + } + + if (old_mask != new_mask) { + undo_redo->create_action(TTR("Edit Tile Bitmask")); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask); + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); + } + } + } else { + if ((erasing && mb->get_button_index() == BUTTON_RIGHT) || (!erasing && mb->get_button_index() == BUTTON_LEFT)) { + dragging = false; + erasing = false; + alternative = false; + } + } + } + if (mm.is_valid()) { + if (dragging && current_tile_region.has_point(mm->get_position())) { + Vector2 coord((int)((mm->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mm->get_position().y - current_tile_region.position.y) / (spacing + size.y))); + Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); + pos = mm->get_position() - (pos + current_tile_region.position); + uint32_t bit = 0; + if (tileset->autotile_get_bitmask_mode(get_current_tile()) == RTileSet::BITMASK_2X2) { + if (pos.x < size.x / 2) { + if (pos.y < size.y / 2) { + bit = RTileSet::BIND_TOPLEFT; + } else { + bit = RTileSet::BIND_BOTTOMLEFT; + } + } else { + if (pos.y < size.y / 2) { + bit = RTileSet::BIND_TOPRIGHT; + } else { + bit = RTileSet::BIND_BOTTOMRIGHT; + } + } + } else { + if (pos.x < size.x / 3) { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOPLEFT; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOMLEFT; + } else { + bit = RTileSet::BIND_LEFT; + } + } else if (pos.x > (size.x / 3) * 2) { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOPRIGHT; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOMRIGHT; + } else { + bit = RTileSet::BIND_RIGHT; + } + } else { + if (pos.y < size.y / 3) { + bit = RTileSet::BIND_TOP; + } else if (pos.y > (size.y / 3) * 2) { + bit = RTileSet::BIND_BOTTOM; + } else { + bit = RTileSet::BIND_CENTER; + } + } + } + + uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord); + uint32_t new_mask = old_mask; + if (alternative) { + new_mask &= ~bit; + new_mask |= (bit << 16); + } else if (erasing) { + new_mask &= ~bit; + new_mask &= ~(bit << 16); + } else { + new_mask |= bit; + new_mask &= ~(bit << 16); + } + if (old_mask != new_mask) { + undo_redo->create_action(TTR("Edit Tile Bitmask")); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask); + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); + } + } + } + } break; + case EDITMODE_COLLISION: + case EDITMODE_OCCLUSION: + case EDITMODE_NAVIGATION: + case EDITMODE_PRIORITY: + case EDITMODE_Z_INDEX: { + Vector2 shape_anchor = Vector2(0, 0); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + shape_anchor = edited_shape_coord; + shape_anchor.x *= (size.x + spacing); + shape_anchor.y *= (size.y + spacing); + } + + const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + shape_anchor += current_tile_region.position; + if (tools[TOOL_SELECT]->is_pressed()) { + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) { + int grabbed_point = get_grabbed_point(mb->get_position(), grab_threshold); + + if (grabbed_point >= 0) { + dragging_point = grabbed_point; + workspace->update(); + return; + } + } + if ((tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) && current_tile_region.has_point(mb->get_position())) { + Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); + if (edited_shape_coord != coord) { + edited_shape_coord = coord; + _select_edited_shape_coord(); + } + } + workspace->update(); + } else if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (edit_mode == EDITMODE_COLLISION) { + if (dragging_point >= 0) { + dragging_point = -1; + + Vector points; + + for (int i = 0; i < current_shape.size(); i++) { + Vector2 p = current_shape[i]; + if (tools[TOOL_GRID_SNAP]->is_pressed() || tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) { + p = snap_point(p); + } + points.push_back(p - shape_anchor); + } + + undo_redo->create_action(TTR("Edit Collision Polygon")); + _set_edited_shape_points(points); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + } else if (edit_mode == EDITMODE_OCCLUSION) { + if (dragging_point >= 0) { + dragging_point = -1; + + PoolVector polygon; + polygon.resize(current_shape.size()); + PoolVector::Write w = polygon.write(); + + for (int i = 0; i < current_shape.size(); i++) { + w[i] = current_shape[i] - shape_anchor; + } + + w.release(); + + undo_redo->create_action(TTR("Edit Occlusion Polygon")); + undo_redo->add_do_method(edited_occlusion_shape.ptr(), "set_polygon", polygon); + undo_redo->add_undo_method(edited_occlusion_shape.ptr(), "set_polygon", edited_occlusion_shape->get_polygon()); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + } else if (edit_mode == EDITMODE_NAVIGATION) { + if (dragging_point >= 0) { + dragging_point = -1; + + PoolVector polygon; + Vector indices; + polygon.resize(current_shape.size()); + PoolVector::Write w = polygon.write(); + + for (int i = 0; i < current_shape.size(); i++) { + w[i] = current_shape[i] - shape_anchor; + indices.push_back(i); + } + + w.release(); + + undo_redo->create_action(TTR("Edit Navigation Polygon")); + undo_redo->add_do_method(edited_navigation_shape.ptr(), "set_vertices", polygon); + undo_redo->add_undo_method(edited_navigation_shape.ptr(), "set_vertices", edited_navigation_shape->get_vertices()); + undo_redo->add_do_method(edited_navigation_shape.ptr(), "clear_polygons"); + undo_redo->add_undo_method(edited_navigation_shape.ptr(), "clear_polygons"); + undo_redo->add_do_method(edited_navigation_shape.ptr(), "add_polygon", indices); + undo_redo->add_undo_method(edited_navigation_shape.ptr(), "add_polygon", edited_navigation_shape->get_polygon(0)); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + } + } + } else if (mm.is_valid()) { + if (dragging_point >= 0) { + current_shape.set(dragging_point, snap_point(mm->get_position())); + workspace->update(); + } + } + } else if (tools[SHAPE_NEW_POLYGON]->is_pressed()) { + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + Vector2 pos = mb->get_position(); + pos = snap_point(pos); + if (creating_shape) { + if (current_shape.size() > 2) { + if (is_within_grabbing_distance_of_first_point(mb->get_position(), grab_threshold)) { + close_shape(shape_anchor); + workspace->update(); + return; + } + } + current_shape.push_back(pos); + workspace->update(); + } else { + creating_shape = true; + _set_edited_collision_shape(Ref()); + current_shape.resize(0); + current_shape.push_back(snap_point(pos)); + workspace->update(); + } + } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + if (creating_shape) { + creating_shape = false; + _select_edited_shape_coord(); + workspace->update(); + } + } + } else if (mm.is_valid()) { + if (creating_shape) { + workspace->update(); + } + } + } else if (tools[SHAPE_NEW_RECTANGLE]->is_pressed()) { + if (mb.is_valid()) { + if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + _set_edited_collision_shape(Ref()); + current_shape.resize(0); + Vector2 pos = mb->get_position(); + pos = snap_point(pos); + current_shape.push_back(pos); + current_shape.push_back(pos); + current_shape.push_back(pos); + current_shape.push_back(pos); + creating_shape = true; + workspace->update(); + return; + } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + if (creating_shape) { + creating_shape = false; + _select_edited_shape_coord(); + workspace->update(); + } + } else if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (creating_shape) { + // if the first two corners are within grabbing distance of one another, expand the rect to fill the tile + if (is_within_grabbing_distance_of_first_point(current_shape[1], grab_threshold)) { + current_shape.set(0, snap_point(shape_anchor)); + current_shape.set(1, snap_point(shape_anchor + Vector2(current_tile_region.size.x, 0))); + current_shape.set(2, snap_point(shape_anchor + current_tile_region.size)); + current_shape.set(3, snap_point(shape_anchor + Vector2(0, current_tile_region.size.y))); + } + + close_shape(shape_anchor); + workspace->update(); + return; + } + } + } else if (mm.is_valid()) { + if (creating_shape) { + Vector2 pos = mm->get_position(); + pos = snap_point(pos); + Vector2 p = current_shape[2]; + current_shape.set(3, snap_point(Vector2(pos.x, p.y))); + current_shape.set(0, snap_point(pos)); + current_shape.set(1, snap_point(Vector2(p.x, pos.y))); + workspace->update(); + } + } + } + } break; + default: { + } + } + } + } +} + +void RTileSetEditor::_on_tool_clicked(int p_tool) { + if (p_tool == BITMASK_COPY) { + bitmask_map_copy = tileset->autotile_get_bitmask_map(get_current_tile()); + } else if (p_tool == BITMASK_PASTE) { + undo_redo->create_action(TTR("Paste Tile Bitmask")); + undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); + undo_redo->add_undo_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); + for (Map::Element *E = bitmask_map_copy.front(); E; E = E->next()) { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); + } + for (Map::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); + } + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); + } else if (p_tool == BITMASK_CLEAR) { + undo_redo->create_action(TTR("Clear Tile Bitmask")); + undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); + for (Map::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); + } + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); + } else if (p_tool == SHAPE_TOGGLE_TYPE) { + if (edited_collision_shape.is_valid()) { + Ref convex = edited_collision_shape; + Ref concave = edited_collision_shape; + Ref previous_shape = edited_collision_shape; + Array sd = tileset->call("tile_get_shapes", get_current_tile()); + + if (convex.is_valid()) { + // Make concave. + undo_redo->create_action(TTR("Make Polygon Concave")); + Ref _concave = memnew(ConcavePolygonShape2D); + edited_collision_shape = _concave; + _set_edited_shape_points(_get_collision_shape_points(convex)); + } else if (concave.is_valid()) { + // Make convex. + undo_redo->create_action(TTR("Make Polygon Convex")); + Ref _convex = memnew(ConvexPolygonShape2D); + edited_collision_shape = _convex; + _set_edited_shape_points(_get_collision_shape_points(concave)); + } + for (int i = 0; i < sd.size(); i++) { + if (sd[i].get("shape") == previous_shape) { + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); + sd.remove(i); + break; + } + } + + undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D(), false, edited_shape_coord); + } else { + undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D()); + } + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + + _update_toggle_shape_button(); + workspace->update(); + workspace_container->update(); + helper->_change_notify(""); + } + } else if (p_tool == SELECT_NEXT) { + _select_next_shape(); + } else if (p_tool == SELECT_PREVIOUS) { + _select_previous_shape(); + } else if (p_tool == SHAPE_DELETE) { + if (creating_shape) { + creating_shape = false; + current_shape.resize(0); + workspace->update(); + } else { + switch (edit_mode) { + case EDITMODE_REGION: { + int t_id = get_current_tile(); + if (workspace_mode == WORKSPACE_EDIT && t_id >= 0) { + undo_redo->create_action(TTR("Remove Tile")); + undo_redo->add_do_method(tileset.ptr(), "remove_tile", t_id); + _undo_tile_removal(t_id); + undo_redo->add_do_method(this, "_validate_current_tile_id"); + + Rect2 tile_region = tileset->tile_get_region(get_current_tile()); + Size2 tile_workspace_size = tile_region.position + tile_region.size; + if (tile_workspace_size.x > get_current_texture()->get_size().x || tile_workspace_size.y > get_current_texture()->get_size().y) { + undo_redo->add_do_method(this, "update_workspace_minsize"); + undo_redo->add_undo_method(this, "update_workspace_minsize"); + } + + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->add_do_method(workspace_overlay, "update"); + undo_redo->add_undo_method(workspace_overlay, "update"); + undo_redo->commit_action(); + } + tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); + workspace_mode = WORKSPACE_EDIT; + update_workspace_tile_mode(); + } break; + case EDITMODE_COLLISION: { + if (!edited_collision_shape.is_null()) { + // Necessary to get the version that returns a Array instead of a Vector. + Array sd = tileset->call("tile_get_shapes", get_current_tile()); + for (int i = 0; i < sd.size(); i++) { + if (sd[i].get("shape") == edited_collision_shape) { + undo_redo->create_action(TTR("Remove Collision Polygon")); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); + sd.remove(i); + undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + break; + } + } + } + } break; + case EDITMODE_OCCLUSION: { + if (!edited_occlusion_shape.is_null()) { + undo_redo->create_action(TTR("Remove Occlusion Polygon")); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), Ref()); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile())); + } else { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), Ref(), edited_shape_coord); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord); + } + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + } break; + case EDITMODE_NAVIGATION: { + if (!edited_navigation_shape.is_null()) { + undo_redo->create_action(TTR("Remove Navigation Polygon")); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), Ref()); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile())); + } else { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), Ref(), edited_shape_coord); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord); + } + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + } break; + default: { + } + } + } + } else if (p_tool == TOOL_SELECT || p_tool == SHAPE_NEW_POLYGON || p_tool == SHAPE_NEW_RECTANGLE) { + if (creating_shape) { + // Cancel Creation + creating_shape = false; + current_shape.resize(0); + workspace->update(); + } + } +} + +void RTileSetEditor::_on_priority_changed(float val) { + if ((int)val == tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)) { + return; + } + + undo_redo->create_action(TTR("Edit Tile Priority")); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, (int)val); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)); + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); +} + +void RTileSetEditor::_on_z_index_changed(float val) { + if ((int)val == tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)) { + return; + } + + undo_redo->create_action(TTR("Edit Tile Z Index")); + undo_redo->add_do_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, (int)val); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)); + undo_redo->add_do_method(workspace, "update"); + undo_redo->add_undo_method(workspace, "update"); + undo_redo->commit_action(); +} + +void RTileSetEditor::_on_grid_snap_toggled(bool p_val) { + helper->set_snap_options_visible(p_val); + workspace->update(); +} + +Vector RTileSetEditor::_get_collision_shape_points(const Ref &p_shape) { + Ref convex = p_shape; + Ref concave = p_shape; + if (convex.is_valid()) { + return convex->get_points(); + } else if (concave.is_valid()) { + Vector points; + for (int i = 0; i < concave->get_segments().size(); i += 2) { + points.push_back(concave->get_segments()[i]); + } + return points; + } else { + return Vector(); + } +} + +Vector RTileSetEditor::_get_edited_shape_points() { + return _get_collision_shape_points(edited_collision_shape); +} + +void RTileSetEditor::_set_edited_shape_points(const Vector &points) { + Ref convex = edited_collision_shape; + Ref concave = edited_collision_shape; + if (convex.is_valid()) { + undo_redo->add_do_method(convex.ptr(), "set_points", points); + undo_redo->add_undo_method(convex.ptr(), "set_points", _get_edited_shape_points()); + } else if (concave.is_valid() && points.size() > 1) { + PoolVector2Array segments; + for (int i = 0; i < points.size() - 1; i++) { + segments.push_back(points[i]); + segments.push_back(points[i + 1]); + } + segments.push_back(points[points.size() - 1]); + segments.push_back(points[0]); + undo_redo->add_do_method(concave.ptr(), "set_segments", segments); + undo_redo->add_undo_method(concave.ptr(), "set_segments", concave->get_segments()); + } +} + +void RTileSetEditor::_update_tile_data() { + current_tile_data.clear(); + if (get_current_tile() < 0) { + return; + } + + Vector sd = tileset->tile_get_shapes(get_current_tile()); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + SubtileData data; + for (int i = 0; i < sd.size(); i++) { + data.collisions.push_back(sd[i].shape); + } + data.navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile()); + data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); + current_tile_data[Vector2i()] = data; + } else { + Vector2 cell_count = _get_subtiles_count(get_current_tile()); + for (int y = 0; y < cell_count.y; y++) { + for (int x = 0; x < cell_count.x; x++) { + SubtileData data; + Vector2i coord(x, y); + for (int i = 0; i < sd.size(); i++) { + if (sd[i].autotile_coord == coord) { + data.collisions.push_back(sd[i].shape); + } + } + data.navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord); + data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); + current_tile_data[coord] = data; + } + } + } +} + +void RTileSetEditor::_update_toggle_shape_button() { + Ref convex = edited_collision_shape; + Ref concave = edited_collision_shape; + separator_shape_toggle->show(); + tools[SHAPE_TOGGLE_TYPE]->show(); + if (edit_mode != EDITMODE_COLLISION || !edited_collision_shape.is_valid()) { + separator_shape_toggle->hide(); + tools[SHAPE_TOGGLE_TYPE]->hide(); + } else if (concave.is_valid()) { + tools[SHAPE_TOGGLE_TYPE]->set_icon(get_icon("ConvexPolygonShape2D", "EditorIcons")); + tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Convex")); + } else if (convex.is_valid()) { + tools[SHAPE_TOGGLE_TYPE]->set_icon(get_icon("ConcavePolygonShape2D", "EditorIcons")); + tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Concave")); + } else { + // Shouldn't happen + separator_shape_toggle->hide(); + tools[SHAPE_TOGGLE_TYPE]->hide(); + } +} + +void RTileSetEditor::_select_next_tile() { + Array tiles = _get_tiles_in_current_texture(true); + if (tiles.size() == 0) { + set_current_tile(-1); + } else if (get_current_tile() == -1) { + set_current_tile(tiles[0]); + } else { + int index = tiles.find(get_current_tile()); + if (index < 0) { + set_current_tile(tiles[0]); + } else if (index == tiles.size() - 1) { + set_current_tile(tiles[0]); + } else { + set_current_tile(tiles[index + 1]); + } + } + if (get_current_tile() == -1) { + return; + } else if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + return; + } else { + switch (edit_mode) { + case EDITMODE_COLLISION: + case EDITMODE_OCCLUSION: + case EDITMODE_NAVIGATION: + case EDITMODE_PRIORITY: + case EDITMODE_Z_INDEX: { + edited_shape_coord = Vector2(); + _select_edited_shape_coord(); + } break; + default: { + } + } + } +} + +void RTileSetEditor::_select_previous_tile() { + Array tiles = _get_tiles_in_current_texture(true); + if (tiles.size() == 0) { + set_current_tile(-1); + } else if (get_current_tile() == -1) { + set_current_tile(tiles[tiles.size() - 1]); + } else { + int index = tiles.find(get_current_tile()); + if (index <= 0) { + set_current_tile(tiles[tiles.size() - 1]); + } else { + set_current_tile(tiles[index - 1]); + } + } + if (get_current_tile() == -1) { + return; + } else if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + return; + } else { + switch (edit_mode) { + case EDITMODE_COLLISION: + case EDITMODE_OCCLUSION: + case EDITMODE_NAVIGATION: + case EDITMODE_PRIORITY: + case EDITMODE_Z_INDEX: { + edited_shape_coord = _get_subtiles_count(get_current_tile()) - Vector2(1, 1); + _select_edited_shape_coord(); + } break; + default: { + } + } + } +} + +Array RTileSetEditor::_get_tiles_in_current_texture(bool sorted) { + Array a; + List all_tiles; + if (!get_current_texture().is_valid()) { + return a; + } + tileset->get_tile_list(&all_tiles); + for (int i = 0; i < all_tiles.size(); i++) { + if (tileset->tile_get_texture(all_tiles[i]) == get_current_texture()) { + a.push_back(all_tiles[i]); + } + } + if (sorted) { + a.sort_custom(this, "_sort_tiles"); + } + return a; +} + +bool RTileSetEditor::_sort_tiles(Variant p_a, Variant p_b) { + int a = p_a; + int b = p_b; + + Vector2 pos_a = tileset->tile_get_region(a).position; + Vector2 pos_b = tileset->tile_get_region(b).position; + if (pos_a.y < pos_b.y) { + return true; + + } else if (pos_a.y == pos_b.y) { + return (pos_a.x < pos_b.x); + } else { + return false; + } +} + +Vector2 RTileSetEditor::_get_subtiles_count(int p_tile_id) { + const int spacing = tileset->autotile_get_spacing(p_tile_id); + const Vector2 region_size = tileset->tile_get_region(p_tile_id).size; + const Vector2 subtile_size = tileset->autotile_get_size(p_tile_id); + // In case of not perfect fit the last row/column is allowed to exceed the tile region. + // The return value is the biggest integer-only `(m, n)` satisfying the formula: + // (m, n) * subtile_size + (m - 1, n - 1) * spacing < region_size + subtile_size + Vector2 mn = Vector2(1, 1) + (region_size / (subtile_size + Vector2(spacing, spacing))); + return mn == mn.floor() ? mn.floor() - Vector2(1, 1) : mn.floor(); +} + +void RTileSetEditor::_select_next_subtile() { + if (get_current_tile() == -1) { + _select_next_tile(); + return; + } + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + _select_next_tile(); + } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) { + _select_next_tile(); + } else { + Vector2 cell_count = _get_subtiles_count(get_current_tile()); + if (edited_shape_coord.x >= cell_count.x - 1 && edited_shape_coord.y >= cell_count.y - 1) { + _select_next_tile(); + } else { + edited_shape_coord.x++; + if (edited_shape_coord.x > cell_count.x - 1) { + edited_shape_coord.x = 0; + edited_shape_coord.y++; + } + _select_edited_shape_coord(); + } + } +} + +void RTileSetEditor::_select_previous_subtile() { + if (get_current_tile() == -1) { + _select_previous_tile(); + return; + } + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + _select_previous_tile(); + } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) { + _select_previous_tile(); + } else { + Vector2 cell_count = _get_subtiles_count(get_current_tile()); + if (edited_shape_coord.x <= 0 && edited_shape_coord.y <= 0) { + _select_previous_tile(); + } else { + edited_shape_coord.x--; + if (edited_shape_coord.x < 0) { + edited_shape_coord.x = cell_count.x - 1; + edited_shape_coord.y--; + } + _select_edited_shape_coord(); + } + } +} + +void RTileSetEditor::_select_next_shape() { + if (get_current_tile() == -1) { + _select_next_subtile(); + } else if (edit_mode != EDITMODE_COLLISION) { + _select_next_subtile(); + } else { + Vector2i edited_coord = Vector2i(); + if (tileset->tile_get_tile_mode(get_current_tile()) != RTileSet::SINGLE_TILE) { + edited_coord = Vector2i(edited_shape_coord); + } + SubtileData data = current_tile_data[edited_coord]; + if (data.collisions.size() == 0) { + _select_next_subtile(); + } else { + int index = data.collisions.find(edited_collision_shape); + if (index < 0) { + _set_edited_collision_shape(data.collisions[0]); + } else if (index == data.collisions.size() - 1) { + _select_next_subtile(); + } else { + _set_edited_collision_shape(data.collisions[index + 1]); + } + } + current_shape.resize(0); + Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); + current_tile_region.position += WORKSPACE_MARGIN; + + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + Vector2 shape_anchor = edited_shape_coord; + shape_anchor.x *= (size.x + spacing); + shape_anchor.y *= (size.y + spacing); + current_tile_region.position += shape_anchor; + + if (edited_collision_shape.is_valid()) { + for (int i = 0; i < _get_edited_shape_points().size(); i++) { + current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); + } + } + workspace->update(); + workspace_container->update(); + helper->_change_notify(""); + } +} + +void RTileSetEditor::_select_previous_shape() { + if (get_current_tile() == -1) { + _select_previous_subtile(); + if (get_current_tile() != -1 && edit_mode == EDITMODE_COLLISION) { + SubtileData data = current_tile_data[Vector2i(edited_shape_coord)]; + if (data.collisions.size() > 1) { + _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); + } + } else { + return; + } + } else if (edit_mode != EDITMODE_COLLISION) { + _select_previous_subtile(); + } else { + Vector2i edited_coord = Vector2i(); + if (tileset->tile_get_tile_mode(get_current_tile()) != RTileSet::SINGLE_TILE) { + edited_coord = Vector2i(edited_shape_coord); + } + SubtileData data = current_tile_data[edited_coord]; + if (data.collisions.size() == 0) { + _select_previous_subtile(); + data = current_tile_data[Vector2i(edited_shape_coord)]; + if (data.collisions.size() > 1) { + _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); + } + } else { + int index = data.collisions.find(edited_collision_shape); + if (index < 0) { + _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); + } else if (index == 0) { + _select_previous_subtile(); + data = current_tile_data[Vector2i(edited_shape_coord)]; + if (data.collisions.size() > 1) { + _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); + } + } else { + _set_edited_collision_shape(data.collisions[index - 1]); + } + } + + current_shape.resize(0); + Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); + current_tile_region.position += WORKSPACE_MARGIN; + + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + Vector2 shape_anchor = edited_shape_coord; + shape_anchor.x *= (size.x + spacing); + shape_anchor.y *= (size.y + spacing); + current_tile_region.position += shape_anchor; + + if (edited_collision_shape.is_valid()) { + for (int i = 0; i < _get_edited_shape_points().size(); i++) { + current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); + } + } + workspace->update(); + workspace_container->update(); + helper->_change_notify(""); + } +} + +void RTileSetEditor::_set_edited_collision_shape(const Ref &p_shape) { + edited_collision_shape = p_shape; + _update_toggle_shape_button(); +} + +void RTileSetEditor::_set_snap_step(Vector2 p_val) { + snap_step.x = CLAMP(p_val.x, 1, 1024); + snap_step.y = CLAMP(p_val.y, 1, 1024); + workspace->update(); +} + +void RTileSetEditor::_set_snap_off(Vector2 p_val) { + snap_offset.x = CLAMP(p_val.x, 0, 1024 + WORKSPACE_MARGIN.x); + snap_offset.y = CLAMP(p_val.y, 0, 1024 + WORKSPACE_MARGIN.y); + workspace->update(); +} + +void RTileSetEditor::_set_snap_sep(Vector2 p_val) { + snap_separation.x = CLAMP(p_val.x, 0, 1024); + snap_separation.y = CLAMP(p_val.y, 0, 1024); + workspace->update(); +} + +void RTileSetEditor::_validate_current_tile_id() { + if (get_current_tile() >= 0 && !tileset->has_tile(get_current_tile())) { + set_current_tile(-1); + } +} + +void RTileSetEditor::_select_edited_shape_coord() { + select_coord(edited_shape_coord); +} + +void RTileSetEditor::_undo_tile_removal(int p_id) { + undo_redo->add_undo_method(tileset.ptr(), "create_tile", p_id); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_name", p_id, tileset->tile_get_name(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_normal_map", p_id, tileset->tile_get_normal_map(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture_offset", p_id, tileset->tile_get_texture_offset(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_material", p_id, tileset->tile_get_material(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_modulate", p_id, tileset->tile_get_modulate(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_occluder_offset", p_id, tileset->tile_get_occluder_offset(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon_offset", p_id, tileset->tile_get_navigation_polygon_offset(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_offset", p_id, 0, tileset->tile_get_shape_offset(p_id, 0)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_transform", p_id, 0, tileset->tile_get_shape_transform(p_id, 0)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_z_index", p_id, tileset->tile_get_z_index(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture", p_id, tileset->tile_get_texture(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", p_id, tileset->tile_get_region(p_id)); + // Necessary to get the version that returns a Array instead of a Vector. + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", p_id, tileset->call("tile_get_shapes", p_id)); + if (tileset->tile_get_tile_mode(p_id) == RTileSet::SINGLE_TILE) { + undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", p_id, tileset->tile_get_light_occluder(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", p_id, tileset->tile_get_navigation_polygon(p_id)); + } else { + Map> oclusion_map = tileset->autotile_get_light_oclusion_map(p_id); + for (Map>::Element *E = oclusion_map.front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", p_id, E->value(), E->key()); + } + Map> navigation_map = tileset->autotile_get_navigation_map(p_id); + for (Map>::Element *E = navigation_map.front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", p_id, E->value(), E->key()); + } + Map bitmask_map = tileset->autotile_get_bitmask_map(p_id); + for (Map::Element *E = bitmask_map.front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", p_id, E->key(), E->value()); + } + Map priority_map = tileset->autotile_get_priority_map(p_id); + for (Map::Element *E = priority_map.front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", p_id, E->key(), E->value()); + } + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", p_id, tileset->autotile_get_icon_coordinate(p_id)); + Map z_map = tileset->autotile_get_z_index_map(p_id); + for (Map::Element *E = z_map.front(); E; E = E->next()) { + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", p_id, E->key(), E->value()); + } + undo_redo->add_undo_method(tileset.ptr(), "tile_set_tile_mode", p_id, tileset->tile_get_tile_mode(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_size", p_id, tileset->autotile_get_size(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_spacing", p_id, tileset->autotile_get_spacing(p_id)); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask_mode", p_id, tileset->autotile_get_bitmask_mode(p_id)); + } +} + +void RTileSetEditor::_zoom_in() { + float scale = workspace->get_scale().x; + if (scale < max_scale) { + scale *= scale_ratio; + workspace->set_scale(Vector2(scale, scale)); + workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale); + workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale); + } +} +void RTileSetEditor::_zoom_out() { + float scale = workspace->get_scale().x; + if (scale > min_scale) { + scale /= scale_ratio; + workspace->set_scale(Vector2(scale, scale)); + workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale); + workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale); + } +} +void RTileSetEditor::_zoom_reset() { + workspace->set_scale(Vector2(1, 1)); + workspace_container->set_custom_minimum_size(workspace->get_rect().size); + workspace_overlay->set_custom_minimum_size(workspace->get_rect().size); +} + +void RTileSetEditor::draw_highlight_current_tile() { + Color shadow_color = Color(0.3, 0.3, 0.3, 0.3); + if ((workspace_mode == WORKSPACE_EDIT && get_current_tile() >= 0) || !edited_region.has_no_area()) { + Rect2 region; + if (edited_region.has_no_area()) { + region = tileset->tile_get_region(get_current_tile()); + region.position += WORKSPACE_MARGIN; + } else { + region = edited_region; + } + + if (region.position.y >= 0) { + workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, region.position.y), shadow_color); + } + if (region.position.x >= 0) { + workspace->draw_rect(Rect2(0, MAX(0, region.position.y), region.position.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color); + } + if (region.position.x + region.size.x <= workspace->get_rect().size.x) { + workspace->draw_rect(Rect2(region.position.x + region.size.x, MAX(0, region.position.y), workspace->get_rect().size.x - region.position.x - region.size.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color); + } + if (region.position.y + region.size.y <= workspace->get_rect().size.y) { + workspace->draw_rect(Rect2(0, region.position.y + region.size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - region.size.y - region.position.y), shadow_color); + } + } else { + workspace->draw_rect(Rect2(Point2(0, 0), workspace->get_rect().size), shadow_color); + } +} + +void RTileSetEditor::draw_highlight_subtile(Vector2 coord, const Vector &other_highlighted) { + Color shadow_color = Color(0.3, 0.3, 0.3, 0.3); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Rect2 region = tileset->tile_get_region(get_current_tile()); + coord.x *= (size.x + spacing); + coord.y *= (size.y + spacing); + coord += region.position; + coord += WORKSPACE_MARGIN; + + if (coord.y >= 0) { + workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, coord.y), shadow_color); + } + if (coord.x >= 0) { + workspace->draw_rect(Rect2(0, MAX(0, coord.y), coord.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color); + } + if (coord.x + size.x <= workspace->get_rect().size.x) { + workspace->draw_rect(Rect2(coord.x + size.x, MAX(0, coord.y), workspace->get_rect().size.x - coord.x - size.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color); + } + if (coord.y + size.y <= workspace->get_rect().size.y) { + workspace->draw_rect(Rect2(0, coord.y + size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - size.y - coord.y), shadow_color); + } + + coord += Vector2(1, 1) / workspace->get_scale().x; + workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0, 0), false); + for (int i = 0; i < other_highlighted.size(); i++) { + coord = other_highlighted[i]; + coord.x *= (size.x + spacing); + coord.y *= (size.y + spacing); + coord += region.position; + coord += WORKSPACE_MARGIN; + coord += Vector2(1, 1) / workspace->get_scale().x; + workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0.5, 0.5), false); + } +} + +void RTileSetEditor::draw_tile_subdivision(int p_id, Color p_color) const { + Color c = p_color; + if (tileset->tile_get_tile_mode(p_id) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == RTileSet::ATLAS_TILE) { + Rect2 region = tileset->tile_get_region(p_id); + Size2 size = tileset->autotile_get_size(p_id); + int spacing = tileset->autotile_get_spacing(p_id); + float j = size.x; + + while (j < region.size.x) { + if (spacing <= 0) { + workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(j, 0), region.position + WORKSPACE_MARGIN + Point2(j, region.size.y), c); + } else { + workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(j, 0), Size2(spacing, region.size.y)), c); + } + j += spacing + size.x; + } + j = size.y; + while (j < region.size.y) { + if (spacing <= 0) { + workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(0, j), region.position + WORKSPACE_MARGIN + Point2(region.size.x, j), c); + } else { + workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(0, j), Size2(region.size.x, spacing)), c); + } + j += spacing + size.y; + } + } +} + +void RTileSetEditor::draw_edited_region_subdivision() const { + Color c = Color(0.3, 0.7, 0.6); + Rect2 region = edited_region; + Size2 size; + int spacing; + bool draw; + + if (workspace_mode == WORKSPACE_EDIT) { + int p_id = get_current_tile(); + size = tileset->autotile_get_size(p_id); + spacing = tileset->autotile_get_spacing(p_id); + draw = tileset->tile_get_tile_mode(p_id) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == RTileSet::ATLAS_TILE; + } else { + size = snap_step; + spacing = snap_separation.x; + draw = workspace_mode != WORKSPACE_CREATE_SINGLE; + } + + if (draw) { + float j = size.x; + while (j < region.size.x) { + if (spacing <= 0) { + workspace->draw_line(region.position + Point2(j, 0), region.position + Point2(j, region.size.y), c); + } else { + workspace->draw_rect(Rect2(region.position + Point2(j, 0), Size2(spacing, region.size.y)), c); + } + j += spacing + size.x; + } + j = size.y; + while (j < region.size.y) { + if (spacing <= 0) { + workspace->draw_line(region.position + Point2(0, j), region.position + Point2(region.size.x, j), c); + } else { + workspace->draw_rect(Rect2(region.position + Point2(0, j), Size2(region.size.x, spacing)), c); + } + j += spacing + size.y; + } + } +} + +void RTileSetEditor::draw_grid_snap() { + if (tools[TOOL_GRID_SNAP]->is_pressed()) { + Color grid_color = Color(0.4, 0, 1); + Size2 s = workspace->get_size(); + + int width_count = Math::floor((s.width - WORKSPACE_MARGIN.x) / (snap_step.x + snap_separation.x)); + int height_count = Math::floor((s.height - WORKSPACE_MARGIN.y) / (snap_step.y + snap_separation.y)); + + int last_p = 0; + if (snap_step.x != 0) { + for (int i = 0; i <= width_count; i++) { + if (i == 0 && snap_offset.x != 0) { + last_p = snap_offset.x; + } + if (snap_separation.x != 0) { + if (i != 0) { + workspace->draw_rect(Rect2(last_p, 0, snap_separation.x, s.height), grid_color); + last_p += snap_separation.x; + } else { + workspace->draw_rect(Rect2(last_p, 0, -snap_separation.x, s.height), grid_color); + } + } else { + workspace->draw_line(Point2(last_p, 0), Point2(last_p, s.height), grid_color); + } + last_p += snap_step.x; + } + } + last_p = 0; + if (snap_step.y != 0) { + for (int i = 0; i <= height_count; i++) { + if (i == 0 && snap_offset.y != 0) { + last_p = snap_offset.y; + } + if (snap_separation.y != 0) { + if (i != 0) { + workspace->draw_rect(Rect2(0, last_p, s.width, snap_separation.y), grid_color); + last_p += snap_separation.y; + } else { + workspace->draw_rect(Rect2(0, last_p, s.width, -snap_separation.y), grid_color); + } + } else { + workspace->draw_line(Point2(0, last_p), Point2(s.width, last_p), grid_color); + } + last_p += snap_step.y; + } + } + } +} + +void RTileSetEditor::draw_polygon_shapes() { + int t_id = get_current_tile(); + if (t_id < 0) { + return; + } + + switch (edit_mode) { + case EDITMODE_COLLISION: { + Vector sd = tileset->tile_get_shapes(t_id); + for (int i = 0; i < sd.size(); i++) { + Vector2 coord = Vector2(0, 0); + Vector2 anchor = Vector2(0, 0); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + coord = sd[i].autotile_coord; + anchor = tileset->autotile_get_size(t_id); + anchor.x += tileset->autotile_get_spacing(t_id); + anchor.y += tileset->autotile_get_spacing(t_id); + anchor.x *= coord.x; + anchor.y *= coord.y; + } + anchor += WORKSPACE_MARGIN; + anchor += tileset->tile_get_region(t_id).position; + Ref shape = sd[i].shape; + if (shape.is_valid()) { + Color c_bg; + Color c_border; + Ref convex = shape; + bool is_convex = convex.is_valid(); + if ((tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE || coord == edited_shape_coord) && sd[i].shape == edited_collision_shape) { + if (is_convex) { + c_bg = Color(0, 1, 1, 0.5); + c_border = Color(0, 1, 1); + } else { + c_bg = Color(0.8, 0, 1, 0.5); + c_border = Color(0.8, 0, 1); + } + } else { + if (is_convex) { + c_bg = Color(0.9, 0.7, 0.07, 0.5); + c_border = Color(0.9, 0.7, 0.07, 1); + + } else { + c_bg = Color(0.9, 0.45, 0.075, 0.5); + c_border = Color(0.9, 0.45, 0.075); + } + } + Vector polygon; + Vector colors; + if (!creating_shape && shape == edited_collision_shape && current_shape.size() > 2) { + for (int j = 0; j < current_shape.size(); j++) { + polygon.push_back(current_shape[j]); + colors.push_back(c_bg); + } + } else { + for (int j = 0; j < _get_collision_shape_points(shape).size(); j++) { + polygon.push_back(_get_collision_shape_points(shape)[j] + anchor); + colors.push_back(c_bg); + } + } + + if (polygon.size() < 3) { + continue; + } + + workspace->draw_polygon(polygon, colors); + + if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + if (!creating_shape && polygon.size() > 1) { + for (int j = 0; j < polygon.size() - 1; j++) { + workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true); + } + workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true); + } + if (shape == edited_collision_shape) { + draw_handles = true; + } + } + } + } + } break; + case EDITMODE_OCCLUSION: { + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + Ref shape = edited_occlusion_shape; + if (shape.is_valid()) { + Color c_bg = Color(0, 1, 1, 0.5); + Color c_border = Color(0, 1, 1); + + Vector polygon; + Vector colors; + Vector2 anchor = WORKSPACE_MARGIN; + anchor += tileset->tile_get_region(get_current_tile()).position; + if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) { + for (int j = 0; j < current_shape.size(); j++) { + polygon.push_back(current_shape[j]); + colors.push_back(c_bg); + } + } else { + for (int j = 0; j < shape->get_polygon().size(); j++) { + polygon.push_back(shape->get_polygon()[j] + anchor); + colors.push_back(c_bg); + } + } + workspace->draw_polygon(polygon, colors); + + if (!creating_shape && polygon.size() > 1) { + for (int j = 0; j < polygon.size() - 1; j++) { + workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true); + } + workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true); + } + if (shape == edited_occlusion_shape) { + draw_handles = true; + } + } + } else { + Map> map = tileset->autotile_get_light_oclusion_map(t_id); + for (Map>::Element *E = map.front(); E; E = E->next()) { + Vector2 coord = E->key(); + Vector2 anchor = tileset->autotile_get_size(t_id); + anchor.x += tileset->autotile_get_spacing(t_id); + anchor.y += tileset->autotile_get_spacing(t_id); + anchor.x *= coord.x; + anchor.y *= coord.y; + anchor += WORKSPACE_MARGIN; + anchor += tileset->tile_get_region(t_id).position; + Ref shape = E->value(); + if (shape.is_valid()) { + Color c_bg; + Color c_border; + if (coord == edited_shape_coord && shape == edited_occlusion_shape) { + c_bg = Color(0, 1, 1, 0.5); + c_border = Color(0, 1, 1); + } else { + c_bg = Color(0.9, 0.7, 0.07, 0.5); + c_border = Color(0.9, 0.7, 0.07, 1); + } + Vector polygon; + Vector colors; + if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) { + for (int j = 0; j < current_shape.size(); j++) { + polygon.push_back(current_shape[j]); + colors.push_back(c_bg); + } + } else { + for (int j = 0; j < shape->get_polygon().size(); j++) { + polygon.push_back(shape->get_polygon()[j] + anchor); + colors.push_back(c_bg); + } + } + workspace->draw_polygon(polygon, colors); + + if (coord == edited_shape_coord) { + if (!creating_shape && polygon.size() > 1) { + for (int j = 0; j < polygon.size() - 1; j++) { + workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true); + } + workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true); + } + if (shape == edited_occlusion_shape) { + draw_handles = true; + } + } + } + } + } + } break; + case EDITMODE_NAVIGATION: { + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + Ref shape = edited_navigation_shape; + + if (shape.is_valid()) { + Color c_bg = Color(0, 1, 1, 0.5); + Color c_border = Color(0, 1, 1); + + Vector polygon; + Vector colors; + Vector2 anchor = WORKSPACE_MARGIN; + anchor += tileset->tile_get_region(get_current_tile()).position; + if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) { + for (int j = 0; j < current_shape.size(); j++) { + polygon.push_back(current_shape[j]); + colors.push_back(c_bg); + } + } else { + PoolVector vertices = shape->get_vertices(); + for (int j = 0; j < shape->get_polygon(0).size(); j++) { + polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor); + colors.push_back(c_bg); + } + } + workspace->draw_polygon(polygon, colors); + + if (!creating_shape && polygon.size() > 1) { + for (int j = 0; j < polygon.size() - 1; j++) { + workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true); + } + workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true); + } + if (shape == edited_navigation_shape) { + draw_handles = true; + } + } + } else { + Map> map = tileset->autotile_get_navigation_map(t_id); + for (Map>::Element *E = map.front(); E; E = E->next()) { + Vector2 coord = E->key(); + Vector2 anchor = tileset->autotile_get_size(t_id); + anchor.x += tileset->autotile_get_spacing(t_id); + anchor.y += tileset->autotile_get_spacing(t_id); + anchor.x *= coord.x; + anchor.y *= coord.y; + anchor += WORKSPACE_MARGIN; + anchor += tileset->tile_get_region(t_id).position; + Ref shape = E->value(); + if (shape.is_valid()) { + Color c_bg; + Color c_border; + if (coord == edited_shape_coord && shape == edited_navigation_shape) { + c_bg = Color(0, 1, 1, 0.5); + c_border = Color(0, 1, 1); + } else { + c_bg = Color(0.9, 0.7, 0.07, 0.5); + c_border = Color(0.9, 0.7, 0.07, 1); + } + Vector polygon; + Vector colors; + if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) { + for (int j = 0; j < current_shape.size(); j++) { + polygon.push_back(current_shape[j]); + colors.push_back(c_bg); + } + } else { + PoolVector vertices = shape->get_vertices(); + for (int j = 0; j < shape->get_polygon(0).size(); j++) { + polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor); + colors.push_back(c_bg); + } + } + workspace->draw_polygon(polygon, colors); + + if (coord == edited_shape_coord) { + if (!creating_shape && polygon.size() > 1) { + for (int j = 0; j < polygon.size() - 1; j++) { + workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1, true); + } + workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1, true); + } + if (shape == edited_navigation_shape) { + draw_handles = true; + } + } + } + } + } + } break; + default: { + } + } + + if (creating_shape && current_shape.size() > 1) { + for (int j = 0; j < current_shape.size() - 1; j++) { + workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1, true); + } + workspace->draw_line(current_shape[current_shape.size() - 1], snap_point(workspace->get_local_mouse_position()), Color(0, 1, 1), 1, true); + draw_handles = true; + } +} + +void RTileSetEditor::close_shape(const Vector2 &shape_anchor) { + creating_shape = false; + + if (edit_mode == EDITMODE_COLLISION) { + if (current_shape.size() >= 3) { + Ref shape = memnew(ConvexPolygonShape2D); + + Vector points; + float p_total = 0; + + for (int i = 0; i < current_shape.size(); i++) { + points.push_back(current_shape[i] - shape_anchor); + + if (i != current_shape.size() - 1) { + p_total += ((current_shape[i + 1].x - current_shape[i].x) * (-current_shape[i + 1].y + (-current_shape[i].y))); + } else { + p_total += ((current_shape[0].x - current_shape[i].x) * (-current_shape[0].y + (-current_shape[i].y))); + } + } + + if (p_total < 0) { + points.invert(); + } + + shape->set_points(points); + + undo_redo->create_action(TTR("Create Collision Polygon")); + // Necessary to get the version that returns a Array instead of a Vector. + Array sd = tileset->call("tile_get_shapes", get_current_tile()); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); + for (int i = 0; i < sd.size(); i++) { + if (sd[i].get("shape") == edited_collision_shape) { + sd.remove(i); + break; + } + } + undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D(), false, edited_shape_coord); + } else { + undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D()); + } + tools[TOOL_SELECT]->set_pressed(true); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } else { + tools[TOOL_SELECT]->set_pressed(true); + workspace->update(); + } + } else if (edit_mode == EDITMODE_OCCLUSION) { + Ref shape = memnew(OccluderPolygon2D); + + PoolVector polygon; + polygon.resize(current_shape.size()); + PoolVector::Write w = polygon.write(); + + for (int i = 0; i < current_shape.size(); i++) { + w[i] = current_shape[i] - shape_anchor; + } + + w.release(); + shape->set_polygon(polygon); + + undo_redo->create_action(TTR("Create Occlusion Polygon")); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), shape, edited_shape_coord); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord); + } else { + undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), shape); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile())); + } + tools[TOOL_SELECT]->set_pressed(true); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } else if (edit_mode == EDITMODE_NAVIGATION) { + Ref shape = memnew(NavigationPolygon); + + PoolVector polygon; + Vector indices; + polygon.resize(current_shape.size()); + PoolVector::Write w = polygon.write(); + + for (int i = 0; i < current_shape.size(); i++) { + w[i] = current_shape[i] - shape_anchor; + indices.push_back(i); + } + + w.release(); + shape->set_vertices(polygon); + shape->add_polygon(indices); + + undo_redo->create_action(TTR("Create Navigation Polygon")); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), shape, edited_shape_coord); + undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord); + } else { + undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), shape); + undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile())); + } + tools[TOOL_SELECT]->set_pressed(true); + undo_redo->add_do_method(this, "_select_edited_shape_coord"); + undo_redo->add_undo_method(this, "_select_edited_shape_coord"); + undo_redo->commit_action(); + } + tileset->_change_notify(""); +} + +void RTileSetEditor::select_coord(const Vector2 &coord) { + _update_tile_data(); + current_shape = PoolVector2Array(); + if (get_current_tile() == -1) { + return; + } + Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); + current_tile_region.position += WORKSPACE_MARGIN; + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + if (edited_collision_shape != tileset->tile_get_shape(get_current_tile(), 0)) { + _set_edited_collision_shape(tileset->tile_get_shape(get_current_tile(), 0)); + } + if (edited_occlusion_shape != tileset->tile_get_light_occluder(get_current_tile())) { + edited_occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); + } + if (edited_navigation_shape != tileset->tile_get_navigation_polygon(get_current_tile())) { + edited_navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile()); + } + + if (edit_mode == EDITMODE_COLLISION) { + current_shape.resize(0); + if (edited_collision_shape.is_valid()) { + for (int i = 0; i < _get_edited_shape_points().size(); i++) { + current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); + } + } + } else if (edit_mode == EDITMODE_OCCLUSION) { + current_shape.resize(0); + if (edited_occlusion_shape.is_valid()) { + for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) { + current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + current_tile_region.position); + } + } + } else if (edit_mode == EDITMODE_NAVIGATION) { + current_shape.resize(0); + if (edited_navigation_shape.is_valid()) { + if (edited_navigation_shape->get_polygon_count() > 0) { + PoolVector vertices = edited_navigation_shape->get_vertices(); + for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) { + current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + current_tile_region.position); + } + } + } + } + } else { + Vector sd = tileset->tile_get_shapes(get_current_tile()); + bool found_collision_shape = false; + for (int i = 0; i < sd.size(); i++) { + if (sd[i].autotile_coord == coord) { + if (edited_collision_shape != sd[i].shape) { + _set_edited_collision_shape(sd[i].shape); + } + found_collision_shape = true; + break; + } + } + if (!found_collision_shape) { + _set_edited_collision_shape(Ref(nullptr)); + } + if (edited_occlusion_shape != tileset->autotile_get_light_occluder(get_current_tile(), coord)) { + edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), coord); + } + if (edited_navigation_shape != tileset->autotile_get_navigation_polygon(get_current_tile(), coord)) { + edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord); + } + + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 size = tileset->autotile_get_size(get_current_tile()); + Vector2 shape_anchor = coord; + shape_anchor.x *= (size.x + spacing); + shape_anchor.y *= (size.y + spacing); + shape_anchor += current_tile_region.position; + if (edit_mode == EDITMODE_COLLISION) { + current_shape.resize(0); + if (edited_collision_shape.is_valid()) { + for (int j = 0; j < _get_edited_shape_points().size(); j++) { + current_shape.push_back(_get_edited_shape_points()[j] + shape_anchor); + } + } + } else if (edit_mode == EDITMODE_OCCLUSION) { + current_shape.resize(0); + if (edited_occlusion_shape.is_valid()) { + for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) { + current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + shape_anchor); + } + } + } else if (edit_mode == EDITMODE_NAVIGATION) { + current_shape.resize(0); + if (edited_navigation_shape.is_valid()) { + if (edited_navigation_shape->get_polygon_count() > 0) { + PoolVector vertices = edited_navigation_shape->get_vertices(); + for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) { + current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + shape_anchor); + } + } + } + } + } + workspace->update(); + workspace_container->update(); + helper->_change_notify(""); +} + +Vector2 RTileSetEditor::snap_point(const Vector2 &point) { + Vector2 p = point; + Vector2 coord = edited_shape_coord; + Vector2 tile_size = tileset->autotile_get_size(get_current_tile()); + int spacing = tileset->autotile_get_spacing(get_current_tile()); + Vector2 anchor = coord; + anchor.x *= (tile_size.x + spacing); + anchor.y *= (tile_size.y + spacing); + anchor += tileset->tile_get_region(get_current_tile()).position; + anchor += WORKSPACE_MARGIN; + Rect2 region(anchor, tile_size); + Rect2 tile_region(tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN, tileset->tile_get_region(get_current_tile()).size); + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + region.position = tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN; + region.size = tileset->tile_get_region(get_current_tile()).size; + } + + if (tools[TOOL_GRID_SNAP]->is_pressed()) { + p.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p.x, snap_separation.x); + p.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p.y, snap_separation.y); + } + + if (tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) { + if (p.x < region.position.x) { + p.x = region.position.x; + } + if (p.y < region.position.y) { + p.y = region.position.y; + } + if (p.x > region.position.x + region.size.x) { + p.x = region.position.x + region.size.x; + } + if (p.y > region.position.y + region.size.y) { + p.y = region.position.y + region.size.y; + } + } + + if (p.x < tile_region.position.x) { + p.x = tile_region.position.x; + } + if (p.y < tile_region.position.y) { + p.y = tile_region.position.y; + } + if (p.x > (tile_region.position.x + tile_region.size.x)) { + p.x = (tile_region.position.x + tile_region.size.x); + } + if (p.y > (tile_region.position.y + tile_region.size.y)) { + p.y = (tile_region.position.y + tile_region.size.y); + } + + return p; +} + +void RTileSetEditor::add_texture(Ref p_texture) { + texture_list->add_item(p_texture->get_path().get_file()); + texture_map.insert(p_texture->get_path(), p_texture); + texture_list->set_item_metadata(texture_list->get_item_count() - 1, p_texture->get_path()); +} + +void RTileSetEditor::remove_texture(Ref p_texture) { + texture_list->remove_item(texture_list->find_metadata(p_texture->get_path())); + texture_map.erase(p_texture->get_path()); + + _validate_current_tile_id(); + + if (!get_current_texture().is_valid()) { + _on_texture_list_selected(-1); + workspace_overlay->update(); + } +} + +void RTileSetEditor::update_texture_list() { + Ref selected_texture = get_current_texture(); + + helper->set_tileset(tileset); + + List ids; + tileset->get_tile_list(&ids); + Vector ids_to_remove; + for (List::Element *E = ids.front(); E; E = E->next()) { + // Clear tiles referencing gone textures (user has been already given the chance to fix broken deps) + if (!tileset->tile_get_texture(E->get()).is_valid()) { + ids_to_remove.push_back(E->get()); + ERR_CONTINUE(!tileset->tile_get_texture(E->get()).is_valid()); + } + + if (!texture_map.has(tileset->tile_get_texture(E->get())->get_path())) { + add_texture(tileset->tile_get_texture(E->get())); + } + } + for (int i = 0; i < ids_to_remove.size(); i++) { + tileset->remove_tile(ids_to_remove[i]); + } + + if (texture_list->get_item_count() > 0 && selected_texture.is_valid()) { + texture_list->select(texture_list->find_metadata(selected_texture->get_path())); + if (texture_list->get_selected_items().size() > 0) { + _on_texture_list_selected(texture_list->get_selected_items()[0]); + } + } else if (get_current_texture().is_valid()) { + _on_texture_list_selected(texture_list->find_metadata(get_current_texture()->get_path())); + } else { + _validate_current_tile_id(); + _on_texture_list_selected(-1); + workspace_overlay->update(); + } + update_texture_list_icon(); + helper->_change_notify(""); +} + +void RTileSetEditor::update_texture_list_icon() { + for (int current_idx = 0; current_idx < texture_list->get_item_count(); current_idx++) { + String path = texture_list->get_item_metadata(current_idx); + texture_list->set_item_icon(current_idx, texture_map[path]); + Size2 texture_size = texture_map[path]->get_size(); + texture_list->set_item_icon_region(current_idx, Rect2(0, 0, MIN(texture_size.x, 150), MIN(texture_size.y, 100))); + } + texture_list->update(); +} + +void RTileSetEditor::update_workspace_tile_mode() { + if (!get_current_texture().is_valid()) { + tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); + workspace_mode = WORKSPACE_EDIT; + for (int i = 1; i < WORKSPACE_MODE_MAX; i++) { + tool_workspacemode[i]->set_disabled(true); + } + tools[SELECT_NEXT]->set_disabled(true); + tools[SELECT_PREVIOUS]->set_disabled(true); + + tools[ZOOM_OUT]->hide(); + tools[ZOOM_1]->hide(); + tools[ZOOM_IN]->hide(); + tools[VISIBLE_INFO]->hide(); + + scroll->hide(); + empty_message->show(); + } else { + for (int i = 1; i < WORKSPACE_MODE_MAX; i++) { + tool_workspacemode[i]->set_disabled(false); + } + tools[SELECT_NEXT]->set_disabled(false); + tools[SELECT_PREVIOUS]->set_disabled(false); + + tools[ZOOM_OUT]->show(); + tools[ZOOM_1]->show(); + tools[ZOOM_IN]->show(); + tools[VISIBLE_INFO]->show(); + + scroll->show(); + empty_message->hide(); + } + + if (workspace_mode != WORKSPACE_EDIT) { + for (int i = 0; i < EDITMODE_MAX; i++) { + tool_editmode[i]->hide(); + } + tool_editmode[EDITMODE_REGION]->show(); + tool_editmode[EDITMODE_REGION]->set_pressed(true); + _on_edit_mode_changed(EDITMODE_REGION); + separator_editmode->show(); + return; + } + + if (get_current_tile() < 0) { + for (int i = 0; i < EDITMODE_MAX; i++) { + tool_editmode[i]->hide(); + } + for (int i = TOOL_SELECT; i < ZOOM_OUT; i++) { + tools[i]->hide(); + } + + separator_editmode->hide(); + separator_bitmask->hide(); + separator_delete->hide(); + separator_grid->hide(); + return; + } + + for (int i = 0; i < EDITMODE_MAX; i++) { + tool_editmode[i]->show(); + } + separator_editmode->show(); + + if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::SINGLE_TILE) { + if (tool_editmode[EDITMODE_ICON]->is_pressed() || tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed() || tool_editmode[EDITMODE_Z_INDEX]->is_pressed()) { + tool_editmode[EDITMODE_COLLISION]->set_pressed(true); + edit_mode = EDITMODE_COLLISION; + } + select_coord(Vector2(0, 0)); + + tool_editmode[EDITMODE_ICON]->hide(); + tool_editmode[EDITMODE_BITMASK]->hide(); + tool_editmode[EDITMODE_PRIORITY]->hide(); + tool_editmode[EDITMODE_Z_INDEX]->hide(); + } else if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::AUTO_TILE) { + if (edit_mode == EDITMODE_ICON) { + select_coord(tileset->autotile_get_icon_coordinate(get_current_tile())); + } else { + _select_edited_shape_coord(); + } + } else if (tileset->tile_get_tile_mode(get_current_tile()) == RTileSet::ATLAS_TILE) { + if (tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed()) { + tool_editmode[EDITMODE_COLLISION]->set_pressed(true); + edit_mode = EDITMODE_COLLISION; + } + if (edit_mode == EDITMODE_ICON) { + select_coord(tileset->autotile_get_icon_coordinate(get_current_tile())); + } else { + _select_edited_shape_coord(); + } + + tool_editmode[EDITMODE_BITMASK]->hide(); + } + _on_edit_mode_changed(edit_mode); +} + +void RTileSetEditor::update_workspace_minsize() { + Size2 workspace_min_size = get_current_texture()->get_size(); + String current_texture_path = get_current_texture()->get_path(); + List tiles; + tileset->get_tile_list(&tiles); + for (List::Element *E = tiles.front(); E; E = E->next()) { + if (tileset->tile_get_texture(E->get())->get_path() != current_texture_path) { + continue; + } + + Rect2i region = tileset->tile_get_region(E->get()); + if (region.position.x + region.size.x > workspace_min_size.x) { + workspace_min_size.x = region.position.x + region.size.x; + } + if (region.position.y + region.size.y > workspace_min_size.y) { + workspace_min_size.y = region.position.y + region.size.y; + } + } + + workspace_container->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2); + workspace_overlay->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2); + // Make sure workspace size is initialized last (otherwise it might be incorrect). + workspace->call_deferred("set_custom_minimum_size", workspace_min_size + WORKSPACE_MARGIN * 2); +} + +void RTileSetEditor::update_edited_region(const Vector2 &end_point) { + edited_region = Rect2(region_from, Size2()); + if (tools[TOOL_GRID_SNAP]->is_pressed()) { + Vector2 grid_coord; + grid_coord = ((region_from - snap_offset) / (snap_step + snap_separation)).floor(); + grid_coord *= (snap_step + snap_separation); + grid_coord += snap_offset; + edited_region.expand_to(grid_coord); + grid_coord += snap_step; + edited_region.expand_to(grid_coord); + + grid_coord = ((end_point - snap_offset) / (snap_step + snap_separation)).floor(); + grid_coord *= (snap_step + snap_separation); + grid_coord += snap_offset; + edited_region.expand_to(grid_coord); + grid_coord += snap_step; + edited_region.expand_to(grid_coord); + } else { + edited_region.expand_to(end_point); + } +} + +int RTileSetEditor::get_current_tile() const { + return current_tile; +} + +void RTileSetEditor::set_current_tile(int p_id) { + if (current_tile != p_id) { + current_tile = p_id; + helper->_change_notify(""); + select_coord(Vector2(0, 0)); + update_workspace_tile_mode(); + if (p_id == -1) { + editor->get_inspector()->edit(tileset.ptr()); + } else { + editor->get_inspector()->edit(helper); + } + } +} + +Ref RTileSetEditor::get_current_texture() { + if (texture_list->get_selected_items().size() == 0) { + return Ref(); + } else { + return texture_map[texture_list->get_item_metadata(texture_list->get_selected_items()[0])]; + } +} + +void RTilesetEditorContext::set_tileset(const Ref &p_tileset) { + tileset = p_tileset; +} + +void RTilesetEditorContext::set_snap_options_visible(bool p_visible) { + snap_options_visible = p_visible; + _change_notify(""); +} + +bool RTilesetEditorContext::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name.operator String(); + + if (name == "options_offset") { + Vector2 snap = p_value; + tileset_editor->_set_snap_off(snap + WORKSPACE_MARGIN); + return true; + } else if (name == "options_step") { + Vector2 snap = p_value; + tileset_editor->_set_snap_step(snap); + return true; + } else if (name == "options_separation") { + Vector2 snap = p_value; + tileset_editor->_set_snap_sep(snap); + return true; + } else if (p_name.operator String().left(5) == "tile_") { + String name2 = p_name.operator String().right(5); + bool v = false; + + if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) { + return false; + } + + if (name2 == "autotile_bitmask_mode") { + tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", p_value, &v); + } else if (name2 == "subtile_size") { + tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", p_value, &v); + } else if (name2 == "subtile_spacing") { + tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", p_value, &v); + } else { + tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/" + name2, p_value, &v); + } + if (v) { + tileset->_change_notify(""); + tileset_editor->workspace->update(); + tileset_editor->workspace_overlay->update(); + } + return v; + } else if (name == "tileset_script") { + tileset->set_script(p_value); + return true; + } else if (name == "selected_collision_one_way") { + Vector sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); + for (int index = 0; index < sd.size(); index++) { + if (sd[index].shape == tileset_editor->edited_collision_shape) { + tileset->tile_set_shape_one_way(tileset_editor->get_current_tile(), index, p_value); + return true; + } + } + return false; + } else if (name == "selected_collision_one_way_margin") { + Vector sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); + for (int index = 0; index < sd.size(); index++) { + if (sd[index].shape == tileset_editor->edited_collision_shape) { + tileset->tile_set_shape_one_way_margin(tileset_editor->get_current_tile(), index, p_value); + return true; + } + } + return false; + } + + tileset_editor->err_dialog->set_text(TTR("This property can't be changed.")); + tileset_editor->err_dialog->popup_centered(Size2(300, 60)); + return false; +} + +bool RTilesetEditorContext::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name.operator String(); + bool v = false; + + if (name == "options_offset") { + r_ret = tileset_editor->snap_offset - WORKSPACE_MARGIN; + v = true; + } else if (name == "options_step") { + r_ret = tileset_editor->snap_step; + v = true; + } else if (name == "options_separation") { + r_ret = tileset_editor->snap_separation; + v = true; + } else if (name.left(5) == "tile_") { + name = name.right(5); + + if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) { + return false; + } + if (!tileset->has_tile(tileset_editor->get_current_tile())) { + return false; + } + + if (name == "autotile_bitmask_mode") { + r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", &v); + } else if (name == "subtile_size") { + r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", &v); + } else if (name == "subtile_spacing") { + r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", &v); + } else { + r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/" + name, &v); + } + return v; + } else if (name == "selected_collision") { + r_ret = tileset_editor->edited_collision_shape; + v = true; + } else if (name == "selected_collision_one_way") { + Vector sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); + for (int index = 0; index < sd.size(); index++) { + if (sd[index].shape == tileset_editor->edited_collision_shape) { + r_ret = sd[index].one_way_collision; + v = true; + break; + } + } + } else if (name == "selected_collision_one_way_margin") { + Vector sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); + for (int index = 0; index < sd.size(); index++) { + if (sd[index].shape == tileset_editor->edited_collision_shape) { + r_ret = sd[index].one_way_collision_margin; + v = true; + break; + } + } + } else if (name == "selected_navigation") { + r_ret = tileset_editor->edited_navigation_shape; + v = true; + } else if (name == "selected_occlusion") { + r_ret = tileset_editor->edited_occlusion_shape; + v = true; + } else if (name == "tileset_script") { + r_ret = tileset->get_script(); + v = true; + } + return v; +} + +void RTilesetEditorContext::_get_property_list(List *p_list) const { + if (snap_options_visible) { + p_list->push_back(PropertyInfo(Variant::NIL, "Snap Options", PROPERTY_HINT_NONE, "options_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_offset")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_step")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_separation")); + } + if (tileset_editor->get_current_tile() >= 0 && !tileset.is_null()) { + int id = tileset_editor->get_current_tile(); + p_list->push_back(PropertyInfo(Variant::NIL, "Selected Tile", PROPERTY_HINT_NONE, "tile_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::STRING, "tile_name")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_tex_offset")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial")); + p_list->push_back(PropertyInfo(Variant::COLOR, "tile_modulate")); + p_list->push_back(PropertyInfo(Variant::INT, "tile_tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE")); + if (tileset->tile_get_tile_mode(id) == RTileSet::AUTO_TILE) { + p_list->push_back(PropertyInfo(Variant::INT, "tile_autotile_bitmask_mode", PROPERTY_HINT_ENUM, "2x2,3x3 (minimal),3x3")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size")); + p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 1024, 1")); + } else if (tileset->tile_get_tile_mode(id) == RTileSet::ATLAS_TILE) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size")); + p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 1024, 1")); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_occluder_offset")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_navigation_offset")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, "tile_z_index", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1")); + } + if (tileset_editor->edit_mode == RTileSetEditor::EDITMODE_COLLISION && tileset_editor->edited_collision_shape.is_valid()) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_collision", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_collision_shape->get_class())); + if (tileset_editor->edited_collision_shape.is_valid()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "selected_collision_one_way", PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::REAL, "selected_collision_one_way_margin", PROPERTY_HINT_NONE)); + } + } + if (tileset_editor->edit_mode == RTileSetEditor::EDITMODE_NAVIGATION && tileset_editor->edited_navigation_shape.is_valid()) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_navigation", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_navigation_shape->get_class())); + } + if (tileset_editor->edit_mode == RTileSetEditor::EDITMODE_OCCLUSION && tileset_editor->edited_occlusion_shape.is_valid()) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_occlusion", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_occlusion_shape->get_class())); + } + if (!tileset.is_null()) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "tileset_script", PROPERTY_HINT_RESOURCE_TYPE, "Script")); + } +} + +void RTilesetEditorContext::_bind_methods() { + ClassDB::bind_method("_hide_script_from_inspector", &RTilesetEditorContext::_hide_script_from_inspector); +} + +RTilesetEditorContext::RTilesetEditorContext(RTileSetEditor *p_tileset_editor) { + tileset_editor = p_tileset_editor; + snap_options_visible = false; +} + +void RTileSetEditorPlugin::edit(Object *p_node) { + if (Object::cast_to(p_node)) { + tileset_editor->edit(Object::cast_to(p_node)); + } +} + +bool RTileSetEditorPlugin::handles(Object *p_node) const { + return p_node->is_class("RTileSet") || p_node->is_class("RTilesetEditorContext"); +} + +void RTileSetEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + tileset_editor_button->show(); + editor->make_bottom_panel_item_visible(tileset_editor); + if (!get_tree()->is_connected("idle_frame", tileset_editor, "_on_workspace_process")) { + get_tree()->connect("idle_frame", tileset_editor, "_on_workspace_process"); + } + } else { + editor->hide_bottom_panel(); + tileset_editor_button->hide(); + if (get_tree()->is_connected("idle_frame", tileset_editor, "_on_workspace_process")) { + get_tree()->disconnect("idle_frame", tileset_editor, "_on_workspace_process"); + } + } +} + +Dictionary RTileSetEditorPlugin::get_state() const { + Dictionary state; + state["snap_offset"] = tileset_editor->snap_offset; + state["snap_step"] = tileset_editor->snap_step; + state["snap_separation"] = tileset_editor->snap_separation; + state["snap_enabled"] = tileset_editor->tools[RTileSetEditor::TOOL_GRID_SNAP]->is_pressed(); + state["keep_inside_tile"] = tileset_editor->tools[RTileSetEditor::SHAPE_KEEP_INSIDE_TILE]->is_pressed(); + state["show_information"] = tileset_editor->tools[RTileSetEditor::VISIBLE_INFO]->is_pressed(); + return state; +} + +void RTileSetEditorPlugin::set_state(const Dictionary &p_state) { + Dictionary state = p_state; + if (state.has("snap_step")) { + tileset_editor->_set_snap_step(state["snap_step"]); + } + + if (state.has("snap_offset")) { + tileset_editor->_set_snap_off(state["snap_offset"]); + } + + if (state.has("snap_separation")) { + tileset_editor->_set_snap_sep(state["snap_separation"]); + } + + if (state.has("snap_enabled")) { + tileset_editor->tools[RTileSetEditor::TOOL_GRID_SNAP]->set_pressed(state["snap_enabled"]); + if (tileset_editor->helper) { + tileset_editor->_on_grid_snap_toggled(state["snap_enabled"]); + } + } + + if (state.has("keep_inside_tile")) { + tileset_editor->tools[RTileSetEditor::SHAPE_KEEP_INSIDE_TILE]->set_pressed(state["keep_inside_tile"]); + } + + if (state.has("show_information")) { + tileset_editor->tools[RTileSetEditor::VISIBLE_INFO]->set_pressed(state["show_information"]); + } +} + +RTileSetEditorPlugin::RTileSetEditorPlugin(EditorNode *p_node) { + editor = p_node; + tileset_editor = memnew(RTileSetEditor(p_node)); + + tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); + tileset_editor->hide(); + + tileset_editor_button = p_node->add_bottom_panel_item(TTR("RTileSet"), tileset_editor); + tileset_editor_button->hide(); +} diff --git a/tile_set_editor_plugin.h b/tile_set_editor_plugin.h new file mode 100644 index 0000000..533ff39 --- /dev/null +++ b/tile_set_editor_plugin.h @@ -0,0 +1,297 @@ +/*************************************************************************/ +/* tile_set_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RTILE_SET_EDITOR_PLUGIN_H +#define RTILE_SET_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "scene/2d/sprite.h" +#include "scene/resources/concave_polygon_shape_2d.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "tile_set.h" + +#define WORKSPACE_MARGIN Vector2(10, 10) +class RTilesetEditorContext; + +class RTileSetEditor : public HSplitContainer { + friend class RTileSetEditorPlugin; + friend class RTilesetEditorContext; + + GDCLASS(RTileSetEditor, HSplitContainer); + + enum TextureToolButtons { + TOOL_TILESET_ADD_TEXTURE, + TOOL_TILESET_REMOVE_TEXTURE, + TOOL_TILESET_CREATE_SCENE, + TOOL_TILESET_MERGE_SCENE, + TOOL_TILESET_MAX + }; + + enum WorkspaceMode { + WORKSPACE_EDIT, + WORKSPACE_CREATE_SINGLE, + WORKSPACE_CREATE_AUTOTILE, + WORKSPACE_CREATE_ATLAS, + WORKSPACE_MODE_MAX + }; + + enum EditMode { + EDITMODE_REGION, + EDITMODE_COLLISION, + EDITMODE_OCCLUSION, + EDITMODE_NAVIGATION, + EDITMODE_BITMASK, + EDITMODE_PRIORITY, + EDITMODE_ICON, + EDITMODE_Z_INDEX, + EDITMODE_MAX + }; + + enum TileSetTools { + SELECT_PREVIOUS, + SELECT_NEXT, + TOOL_SELECT, + BITMASK_COPY, + BITMASK_PASTE, + BITMASK_CLEAR, + SHAPE_NEW_POLYGON, + SHAPE_NEW_RECTANGLE, + SHAPE_TOGGLE_TYPE, + SHAPE_DELETE, + SHAPE_KEEP_INSIDE_TILE, + TOOL_GRID_SNAP, + ZOOM_OUT, + ZOOM_1, + ZOOM_IN, + VISIBLE_INFO, + TOOL_MAX + }; + + struct SubtileData { + Array collisions; + Ref occlusion_shape; + Ref navigation_shape; + }; + + Ref tileset; + RTilesetEditorContext *helper; + EditorNode *editor; + UndoRedo *undo_redo; + + ConfirmationDialog *cd; + AcceptDialog *err_dialog; + EditorFileDialog *texture_dialog; + + ItemList *texture_list; + int option; + ToolButton *tileset_toolbar_buttons[TOOL_TILESET_MAX]; + MenuButton *tileset_toolbar_tools; + Map> texture_map; + + bool creating_shape; + int dragging_point; + bool tile_names_visible; + Vector2 region_from; + Rect2 edited_region; + bool draw_edited_region; + Vector2 edited_shape_coord; + PoolVector2Array current_shape; + Map current_tile_data; + Map bitmask_map_copy; + + Vector2 snap_step; + Vector2 snap_offset; + Vector2 snap_separation; + + Ref edited_collision_shape; + Ref edited_occlusion_shape; + Ref edited_navigation_shape; + + int current_item_index; + Sprite *preview; + ScrollContainer *scroll; + Label *empty_message; + Control *workspace_container; + bool draw_handles; + Control *workspace_overlay; + Control *workspace; + Button *tool_workspacemode[WORKSPACE_MODE_MAX]; + Button *tool_editmode[EDITMODE_MAX]; + HSeparator *separator_editmode; + HBoxContainer *toolbar; + ToolButton *tools[TOOL_MAX]; + VSeparator *separator_shape_toggle; + VSeparator *separator_bitmask; + VSeparator *separator_delete; + VSeparator *separator_grid; + SpinBox *spin_priority; + SpinBox *spin_z_index; + WorkspaceMode workspace_mode; + EditMode edit_mode; + int current_tile; + + float max_scale; + float min_scale; + float scale_ratio; + + void update_texture_list(); + void update_texture_list_icon(); + + void add_texture(Ref p_texture); + void remove_texture(Ref p_texture); + + Ref get_current_texture(); + + static void _import_node(Node *p_node, Ref p_library); + static void _import_scene(Node *p_scene, Ref p_library, bool p_merge); + void _undo_redo_import_scene(Node *p_scene, bool p_merge); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void edit(const Ref &p_tileset); + static Error update_library_file(Node *p_base_scene, Ref ml, bool p_merge = true); + + RTileSetEditor(EditorNode *p_editor); + ~RTileSetEditor(); + +private: + void _on_tileset_toolbar_button_pressed(int p_index); + void _on_tileset_toolbar_confirm(); + void _on_texture_list_selected(int p_index); + void _on_textures_added(const PoolStringArray &p_paths); + void _on_edit_mode_changed(int p_edit_mode); + void _on_workspace_mode_changed(int p_workspace_mode); + void _on_workspace_overlay_draw(); + void _on_workspace_draw(); + void _on_workspace_process(); + void _on_scroll_container_input(const Ref &p_event); + void _on_workspace_input(const Ref &p_ie); + void _on_tool_clicked(int p_tool); + void _on_priority_changed(float val); + void _on_z_index_changed(float val); + void _on_grid_snap_toggled(bool p_val); + Vector _get_collision_shape_points(const Ref &p_shape); + Vector _get_edited_shape_points(); + void _set_edited_shape_points(const Vector &points); + void _update_tile_data(); + void _update_toggle_shape_button(); + void _select_next_tile(); + void _select_previous_tile(); + Array _get_tiles_in_current_texture(bool sorted = false); + bool _sort_tiles(Variant p_a, Variant p_b); + Vector2 _get_subtiles_count(int p_tile_id); + void _select_next_subtile(); + void _select_previous_subtile(); + void _select_next_shape(); + void _select_previous_shape(); + void _set_edited_collision_shape(const Ref &p_shape); + void _set_snap_step(Vector2 p_val); + void _set_snap_off(Vector2 p_val); + void _set_snap_sep(Vector2 p_val); + + void _validate_current_tile_id(); + void _select_edited_shape_coord(); + void _undo_tile_removal(int p_id); + + void _zoom_in(); + void _zoom_out(); + void _zoom_reset(); + + void draw_highlight_current_tile(); + void draw_highlight_subtile(Vector2 coord, const Vector &other_highlighted = Vector()); + void draw_tile_subdivision(int p_id, Color p_color) const; + void draw_edited_region_subdivision() const; + void draw_grid_snap(); + void draw_polygon_shapes(); + void close_shape(const Vector2 &shape_anchor); + void select_coord(const Vector2 &coord); + Vector2 snap_point(const Vector2 &point); + void update_workspace_tile_mode(); + void update_workspace_minsize(); + void update_edited_region(const Vector2 &end_point); + int get_grabbed_point(const Vector2 &p_mouse_pos, real_t grab_threshold); + bool is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold); + + int get_current_tile() const; + void set_current_tile(int p_id); +}; + +class RTilesetEditorContext : public Object { + friend class RTileSetEditor; + GDCLASS(RTilesetEditorContext, Object); + + Ref tileset; + RTileSetEditor *tileset_editor; + bool snap_options_visible; + +public: + bool _hide_script_from_inspector() { return true; } + void set_tileset(const Ref &p_tileset); + +private: + void set_snap_options_visible(bool p_visible); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + static void _bind_methods(); + +public: + RTilesetEditorContext(RTileSetEditor *p_tileset_editor); +}; + +class RTileSetEditorPlugin : public EditorPlugin { + GDCLASS(RTileSetEditorPlugin, EditorPlugin); + + RTileSetEditor *tileset_editor; + Button *tileset_editor_button; + EditorNode *editor; + +public: + virtual String get_name() const { return "RTileSet"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_node); + virtual bool handles(Object *p_node) const; + virtual void make_visible(bool p_visible); + void set_state(const Dictionary &p_state); + Dictionary get_state() const; + + RTileSetEditorPlugin(EditorNode *p_node); +}; + +#endif // TILE_SET_EDITOR_PLUGIN_H