1 /*************************************************************************/
2 /*  tile_set_editor_plugin.cpp                                           */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 #include "tile_set_editor_plugin.h"
31 #include "scene/2d/physics_body_2d.h"
32 #include "scene/2d/sprite.h"
edit(const Ref<TileSet> & p_tileset)33 void TileSetEditor::edit(const Ref<TileSet> &p_tileset) {
34 
35 	tileset = p_tileset;
36 }
37 
_import_node(Node * p_node,Ref<TileSet> p_library)38 void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) {
39 
40 	for (int i = 0; i < p_node->get_child_count(); i++) {
41 
42 		Node *child = p_node->get_child(i);
43 
44 		if (!child->cast_to<Sprite>()) {
45 			if (child->get_child_count() > 0) {
46 				_import_node(child, p_library);
47 			}
48 
49 			continue;
50 		}
51 
52 		Sprite *mi = child->cast_to<Sprite>();
53 		Ref<Texture> texture = mi->get_texture();
54 		Ref<CanvasItemMaterial> material = mi->get_material();
55 
56 		if (texture.is_null())
57 			continue;
58 
59 		int id = p_library->find_tile_by_name(mi->get_name());
60 		if (id < 0) {
61 
62 			id = p_library->get_last_unused_tile_id();
63 			p_library->create_tile(id);
64 			p_library->tile_set_name(id, mi->get_name());
65 		}
66 
67 		p_library->tile_set_texture(id, texture);
68 		p_library->tile_set_material(id, material);
69 
70 		p_library->tile_set_modulate(id, mi->get_modulate());
71 
72 		Vector2 phys_offset;
73 		Size2 s;
74 
75 		if (mi->is_region()) {
76 			s = mi->get_region_rect().size;
77 			p_library->tile_set_region(id, mi->get_region_rect());
78 		} else {
79 			const int frame = mi->get_frame();
80 			const int hframes = mi->get_hframes();
81 			s = texture->get_size() / Size2(hframes, mi->get_vframes());
82 			p_library->tile_set_region(id, Rect2(Vector2(frame % hframes, frame / hframes) * s, s));
83 		}
84 
85 		if (mi->is_centered()) {
86 			phys_offset += -s / 2;
87 		}
88 
89 		Vector<Ref<Shape2D> > collisions;
90 		Ref<NavigationPolygon> nav_poly;
91 		Ref<OccluderPolygon2D> occluder;
92 		bool one_way_ok = true;
93 		Variant one_way_dir;
94 		float one_way_max_depth = 0.0f;
95 
96 		for (int j = 0; j < mi->get_child_count(); j++) {
97 
98 			Node *child2 = mi->get_child(j);
99 
100 			if (child2->cast_to<NavigationPolygonInstance>())
101 				nav_poly = child2->cast_to<NavigationPolygonInstance>()->get_navigation_polygon();
102 
103 			if (child2->cast_to<LightOccluder2D>())
104 				occluder = child2->cast_to<LightOccluder2D>()->get_occluder_polygon();
105 
106 			if (!child2->cast_to<StaticBody2D>())
107 				continue;
108 			StaticBody2D *sb = child2->cast_to<StaticBody2D>();
109 			int shape_count = sb->get_shape_count();
110 			if (shape_count == 0)
111 				continue;
112 			for (int shape_index = 0; shape_index < shape_count; ++shape_index) {
113 				Ref<Shape2D> collision = sb->get_shape(shape_index);
114 				if (collision.is_valid()) {
115 					collisions.push_back(collision);
116 				}
117 			}
118 
119 			phys_offset -= sb->get_pos();
120 
121 			if (one_way_ok) {
122 				Vector2 curr_dir = sb->get_one_way_collision_direction();
123 				float curr_max_depth = sb->get_one_way_collision_max_depth();
124 				if (one_way_dir == Variant()) {
125 					one_way_dir = curr_dir;
126 					one_way_max_depth = curr_max_depth;
127 				} else {
128 					if (curr_dir != one_way_dir || curr_max_depth != one_way_max_depth) {
129 						one_way_ok = false;
130 						WARN_PRINT(String("Mismatch in one-way collision parameters for " + child->get_name()).utf8().get_data());
131 					}
132 				}
133 			}
134 		}
135 
136 		if (collisions.size()) {
137 
138 			p_library->tile_set_shapes(id, collisions);
139 			p_library->tile_set_shape_offset(id, -phys_offset);
140 			if (one_way_ok && one_way_dir != Variant()) {
141 				p_library->tile_set_one_way_collision_direction(id, one_way_dir);
142 				p_library->tile_set_one_way_collision_max_depth(id, one_way_max_depth);
143 			}
144 		} else {
145 			p_library->tile_set_shape_offset(id, Vector2());
146 		}
147 
148 		p_library->tile_set_texture_offset(id, mi->get_offset());
149 		p_library->tile_set_navigation_polygon(id, nav_poly);
150 		p_library->tile_set_light_occluder(id, occluder);
151 		p_library->tile_set_occluder_offset(id, -phys_offset);
152 		p_library->tile_set_navigation_polygon_offset(id, -phys_offset);
153 	}
154 }
155 
_import_scene(Node * p_scene,Ref<TileSet> p_library,bool p_merge)156 void TileSetEditor::_import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge) {
157 	if (!p_merge)
158 		p_library->clear();
159 
160 	_import_node(p_scene, p_library);
161 }
162 
_menu_confirm()163 void TileSetEditor::_menu_confirm() {
164 
165 	switch (option) {
166 
167 		case MENU_OPTION_MERGE_FROM_SCENE:
168 		case MENU_OPTION_CREATE_FROM_SCENE: {
169 
170 			EditorNode *en = editor;
171 			Node *scene = en->get_edited_scene();
172 			if (!scene)
173 				break;
174 
175 			_import_scene(scene, tileset, option == MENU_OPTION_MERGE_FROM_SCENE);
176 
177 		} break;
178 	}
179 }
180 
_name_dialog_confirm(const String & name)181 void TileSetEditor::_name_dialog_confirm(const String &name) {
182 
183 	switch (option) {
184 
185 		case MENU_OPTION_REMOVE_ITEM: {
186 
187 			int id = tileset->find_tile_by_name(name);
188 
189 			if (id < 0 && name.is_valid_integer())
190 				id = name.to_int();
191 
192 			if (tileset->has_tile(id)) {
193 				tileset->remove_tile(id);
194 			} else {
195 				err_dialog->set_text(TTR("Could not find tile:") + " " + name);
196 				err_dialog->popup_centered(Size2(300, 60));
197 			}
198 		} break;
199 	}
200 }
201 
_menu_cbk(int p_option)202 void TileSetEditor::_menu_cbk(int p_option) {
203 
204 	option = p_option;
205 	switch (p_option) {
206 
207 		case MENU_OPTION_ADD_ITEM: {
208 
209 			tileset->create_tile(tileset->get_last_unused_tile_id());
210 		} break;
211 		case MENU_OPTION_REMOVE_ITEM: {
212 
213 			nd->set_title(TTR("Remove Item"));
214 			nd->set_text(TTR("Item name or ID:"));
215 			nd->popup_centered(Size2(300, 95));
216 		} break;
217 		case MENU_OPTION_CREATE_FROM_SCENE: {
218 
219 			cd->set_text(TTR("Create from scene?"));
220 			cd->popup_centered(Size2(300, 60));
221 		} break;
222 		case MENU_OPTION_MERGE_FROM_SCENE: {
223 
224 			cd->set_text(TTR("Merge from scene?"));
225 			cd->popup_centered(Size2(300, 60));
226 		} break;
227 	}
228 }
229 
update_library_file(Node * p_base_scene,Ref<TileSet> ml,bool p_merge)230 Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge) {
231 
232 	_import_scene(p_base_scene, ml, p_merge);
233 	return OK;
234 }
235 
_bind_methods()236 void TileSetEditor::_bind_methods() {
237 
238 	ObjectTypeDB::bind_method("_menu_cbk", &TileSetEditor::_menu_cbk);
239 	ObjectTypeDB::bind_method("_menu_confirm", &TileSetEditor::_menu_confirm);
240 	ObjectTypeDB::bind_method("_name_dialog_confirm", &TileSetEditor::_name_dialog_confirm);
241 }
242 
TileSetEditor(EditorNode * p_editor)243 TileSetEditor::TileSetEditor(EditorNode *p_editor) {
244 
245 	Panel *panel = memnew(Panel);
246 	panel->set_area_as_parent_rect();
247 	add_child(panel);
248 	MenuButton *options = memnew(MenuButton);
249 	panel->add_child(options);
250 	options->set_pos(Point2(1, 1));
251 	options->set_text("Theme");
252 	options->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM);
253 	options->get_popup()->add_item(TTR("Remove Item"), MENU_OPTION_REMOVE_ITEM);
254 	options->get_popup()->add_separator();
255 	options->get_popup()->add_item(TTR("Create from Scene"), MENU_OPTION_CREATE_FROM_SCENE);
256 	options->get_popup()->add_item(TTR("Merge from Scene"), MENU_OPTION_MERGE_FROM_SCENE);
257 	options->get_popup()->connect("item_pressed", this, "_menu_cbk");
258 	editor = p_editor;
259 	cd = memnew(ConfirmationDialog);
260 	add_child(cd);
261 	cd->get_ok()->connect("pressed", this, "_menu_confirm");
262 
263 	nd = memnew(EditorNameDialog);
264 	add_child(nd);
265 	nd->set_hide_on_ok(true);
266 	nd->get_line_edit()->set_margin(MARGIN_TOP, 28);
267 	nd->connect("name_confirmed", this, "_name_dialog_confirm");
268 
269 	err_dialog = memnew(AcceptDialog);
270 	add_child(err_dialog);
271 	err_dialog->set_title(TTR("Error"));
272 }
273 
edit(Object * p_node)274 void TileSetEditorPlugin::edit(Object *p_node) {
275 
276 	if (p_node && p_node->cast_to<TileSet>()) {
277 		tileset_editor->edit(p_node->cast_to<TileSet>());
278 		tileset_editor->show();
279 	} else
280 		tileset_editor->hide();
281 }
282 
handles(Object * p_node) const283 bool TileSetEditorPlugin::handles(Object *p_node) const {
284 
285 	return p_node->is_type("TileSet");
286 }
287 
make_visible(bool p_visible)288 void TileSetEditorPlugin::make_visible(bool p_visible) {
289 
290 	if (p_visible)
291 		tileset_editor->show();
292 	else
293 		tileset_editor->hide();
294 }
295 
TileSetEditorPlugin(EditorNode * p_node)296 TileSetEditorPlugin::TileSetEditorPlugin(EditorNode *p_node) {
297 
298 	tileset_editor = memnew(TileSetEditor(p_node));
299 
300 	p_node->get_viewport()->add_child(tileset_editor);
301 	tileset_editor->set_area_as_parent_rect();
302 	tileset_editor->set_anchor(MARGIN_RIGHT, Control::ANCHOR_END);
303 	tileset_editor->set_anchor(MARGIN_BOTTOM, Control::ANCHOR_BEGIN);
304 	tileset_editor->set_end(Point2(0, 22));
305 	tileset_editor->hide();
306 }
307