1 /* Battle Tanks Game
2  * Copyright (C) 2006-2009 Battle Tanks team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  */
18 
19 /*
20  * Additional rights can be granted beyond the GNU General Public License
21  * on the terms provided in the Exception. If you modify this file,
22  * you may extend this exception to your version of the file,
23  * but you are not obligated to do so. If you do not wish to provide this
24  * exception without modification, you must delete this exception statement
25  * from your version and license this file solely under the GPL without exception.
26 */
27 
28 #include "editor.h"
29 
30 #include "mrt/logger.h"
31 #include "mrt/directory.h"
32 #include "mrt/file.h"
33 #include "sdlx/sdlx.h"
34 
35 #include "window.h"
36 #include "resource_manager.h"
37 #include "animation_model.h"
38 #include "game_monitor.h"
39 
40 #include "object.h"
41 #include "world.h"
42 #include "finder.h"
43 
44 #include "i18n.h"
45 #include "var.h"
46 #include "config.h"
47 #include "hud.h"
48 #include "rt_config.h"
49 
50 #include "tmx/layer.h"
51 #include "tmx/map.h"
52 #include "tmx/generator.h"
53 
54 #include "math/unary.h"
55 
56 #include "open_map_dialog.h"
57 #include "tileset_dialog.h"
58 #include "layer_list_dialog.h"
59 #include "add_object_dialog.h"
60 #include "base_brush.h"
61 #include "layer_item.h"
62 #include "command.h"
63 #include "object_properties.h"
64 #include "tilebox_brush.h"
65 #include "morph_dialog.h"
66 #include "resize_dialog.h"
67 
68 #include "game.h"
69 #include "math/binary.h"
70 
moveObjectHack(Object * object,const v2<int> & screen_pos)71 void Editor::moveObjectHack(Object *object, const v2<int>& screen_pos) {
72 	World->move(object, screen_pos.x + map_pos.x, screen_pos.y + map_pos.y);
73 	World->purge(0);
74 }
75 
addCommand(Command & cmd)76 void Editor::addCommand(Command &cmd) {
77 	undo_queue.push_back(cmd);
78 	redo_queue.clear();
79 }
80 
currentCommand()81 Command &Editor::currentCommand() {
82 	if (undo_queue.empty())
83 		throw_ex(("undo queue is empty"));
84 	return undo_queue.back();
85 }
86 
undo()87 void Editor::undo() {
88 	if (undo_queue.empty())
89 		return;
90 
91 	LOG_DEBUG(("undo"));
92 	undo_queue.back().undo();
93 	redo_queue.push_front(undo_queue.back());
94 	undo_queue.pop_back();
95 	//LOG_DEBUG(("undo. undo queue size = %u", (unsigned)undo_queue.size()));
96 }
97 
redo()98 void Editor::redo() {
99 	if (redo_queue.empty())
100 		return;
101 
102 	LOG_DEBUG(("redo"));
103 	redo_queue.front().exec();
104 	undo_queue.push_back(redo_queue.front());
105 	redo_queue.pop_front();
106 	//LOG_DEBUG(("redo. undo queue size = %u", (unsigned)redo_queue.size()));
107 }
108 
109 
Editor()110 Editor::Editor() :
111 	_map_picker(NULL), _hud(NULL), _brush(NULL), _layer_name_invisible(1.0f, false), _layer_invisible(1.0f, false),
112 	_render_objects(true), _highlight_object(NULL), _dragging(false), _selecting(false) {
113 }
114 
loadMap(const std::string & map)115 void Editor::loadMap(const std::string &map) {
116 TRY {
117 	_render_objects = true;
118 	_dragging = false;
119 	_selecting = false;
120 
121 	LOG_DEBUG(("loading map %s", map.c_str()));
122 	GameMonitor->clear();
123 	World->clear();
124 
125 	GameMonitor->loadMap(NULL, map);
126 
127 	v2<int> map_size = Map->get_size();
128 	_tile_size = Map->getTileSize();
129 	map_pos.w = map_size.x;
130 	map_pos.h = map_size.y;
131 	map_pos.x = map_pos.y = 0;
132 
133 	delete _brush;
134 	_brush = NULL;
135 	undo_queue.clear();
136 	redo_queue.clear();
137 
138 	{
139 		Var bt("bool");
140 		bt.b = true;
141 		Config->setOverride("engine.mark-map-tiles", bt);
142 		Config->setOverride("engine.show-waypoints", bt);
143 	}
144 
145 	_current_layer_z = 0;
146 	updateLayers();
147 	Map->getGenerator()->getPrimaryBoxes(_morph_boxes);
148 } CATCH("loadMap", throw;)
149 }
150 
onTick(float dt)151 bool Editor::onTick(float dt) {
152 	static const Uint8 *keys = SDL_GetKeyState(0);
153 	bool active = (keys[SDLK_LSHIFT] != 0 || keys[SDLK_RSHIFT] != 0 || _layers_dialog->active()) && Map->loaded() && _tileset_dialog->hidden();
154 	_layers_dialog->hide(!active);
155 
156 	tick(dt);
157 	Map->tick(dt);
158 
159 	if (_map_picker->changed()) {
160 		_map_picker->reset();
161 		_map_picker->getMap(_map_base, _map_file);
162 		loadMap(_map_file);
163 	}
164 
165 	if (_tileset_dialog->changed()) {
166 		_morph_dialog->initMap();
167 		_tileset_dialog->reset();
168 		BaseBrush *b = _tileset_dialog->getBrush();
169 		delete _brush;
170 		_brush = b;
171 	}
172 	if (_tileset_dialog->tileset_added()) {
173 		_morph_dialog->initMap();
174 	}
175 
176 	if (_layers_dialog->changed()) {
177 		_layers_dialog->reset();
178 		updateLayers();
179 	}
180 
181 	if (_add_object->changed()) {
182 		_add_object->reset();
183 		std::string classname, animation;
184 		int z;
185 		if (_add_object->get(classname, animation, z)) {
186 			delete _brush;
187 			_brush = NULL;
188 			TRY {
189 				_brush = new ObjectBrush(this, classname, animation, z);
190 			} CATCH("adding object brush", {});
191 		}
192 		/*
193 		if (_add_object->get(classname, animation, z)) {
194 			SDL_WarpMouse(mouse_pos.x, mouse_pos.y);
195 			LOG_DEBUG(("creation of %s:%s requested (z: %d)", classname.c_str(), animation.c_str(), z));
196 
197 			const Animation *a = ResourceManager.get_const()->getAnimation(animation);
198 			v2<int> pos = (mouse_pos.convert<float>() + v2<float>(map_pos.x, map_pos.y) - v2<float>(a->tw, a->th) / 2).convert<int>();
199 
200 			Command cmd(classname, animation, pos, z);
201 			addCommand(cmd);
202 			currentCommand().exec();
203 		}
204 		*/
205 	}
206 
207 	if (_object_properties->changed()) {
208 		std::set<std::string> vars;
209 		_object_properties->get(vars);
210 		Command cmd(Command::ChangeObjectProperties, _object_properties->object, _object_properties->get_z(), Variants(vars));
211 		addCommand(cmd);
212 		currentCommand().exec();
213 		displayStatusMessage(_object_properties->object);
214 
215 		_object_properties->reset();
216 		_object_properties->hide();
217 	}
218 
219 	if (_morph_dialog->changed()) {
220 		_morph_dialog->reset();
221 		int idx = _morph_dialog->get();
222 		if (Map->loaded() && idx < (int)_morph_boxes.size()) {
223 			LOG_DEBUG(("morph brush"));
224 			delete _brush;
225 			_brush = new TileBoxBrush(_morph_boxes[idx].first, _morph_boxes[idx].second);
226 		}
227 	}
228 
229 	{
230 		v2<float> dpos = map_dir;
231 		GET_CONFIG_VALUE("editor.scrolling-speed", int, speed, 500);
232 		dpos *= speed * dt;
233 		map_pos.x += (int)dpos.x;
234 		map_pos.y += (int)dpos.y;
235 	}
236 
237 	World->purge(0);
238 
239 	render(Window->get_surface(), dt);
240 	return true;
241 }
242 
243 
render(sdlx::Surface & surface,const float dt)244 void Editor::render(sdlx::Surface &surface, const float dt) {
245 	sdlx::Rect window_size = surface.get_size();
246 	surface.fill(surface.map_rgb(255, 255, 255));
247 
248 	const bool highlight_layer = !_layer_invisible.tick(dt);
249 	const bool show_layer_name = !_layer_name_invisible.tick(dt);
250 	if (Map->loaded()) {
251 		const sdlx::Rect window_size = Window->get_size();
252 		const v2<int> map_size = Map->get_size();
253 
254 		if (map_pos.x < 0)
255 			map_pos.x = 0;
256 
257 		if (map_pos.y < 0)
258 			map_pos.y = 0;
259 
260 		if (map_pos.x + window_size.w > map_size.x)
261 			map_pos.x = map_size.x - window_size.w;
262 
263 		if (map_pos.y + window_size.h > map_size.y)
264 			map_pos.y = map_size.y - window_size.h;
265 
266 		const bool render_objects = _render_objects && !Map->hasSoloLayers();
267 
268 		if (render_objects)
269 			World->render(surface, map_pos, window_size, -10000, _current_layer_z);
270 		else
271 			Map->render(surface, map_pos, window_size, -10000, _current_layer_z);
272 
273 		if (highlight_layer) {
274 			int p = (int)(_layer_invisible.get() * 6);
275 			if (p & 1) {
276 				if (render_objects)
277 					World->render(surface, map_pos, window_size, _current_layer_z, _current_layer_z + 1);
278 				else
279 					Map->render(surface, map_pos, window_size, _current_layer_z, _current_layer_z + 1);
280 			}
281 		} else {
282 			if (render_objects)
283 				World->render(surface, map_pos, window_size, _current_layer_z, _current_layer_z + 1);
284 			else
285 				Map->render(surface, map_pos, window_size, _current_layer_z, _current_layer_z + 1);
286 		}
287 
288 		if (_brush && _tileset_dialog->hidden()) {
289 			//LOG_DEBUG(("picked up brush %p", _brush));
290 			int mx, my;
291 			SDL_GetMouseState(&mx, &my);
292 			v2<int> window_pos(mx, my);
293 			v2<int> base(map_pos.x, map_pos.y);
294 			v2<int> tile_pos = (base + window_pos) / _tile_size * _tile_size - base;
295 			_brush->render(surface, window_pos, tile_pos);
296 		}
297 		if (render_objects)
298 			World->render(surface, map_pos, window_size, _current_layer_z + 1, 10000);
299 		else
300 			Map->render(surface, map_pos, window_size, _current_layer_z + 1, 10000);
301 
302 		if (_selecting || _selection1.quick_distance(_selection2) >= 1024) {
303 			static sdlx::Surface selection;
304 			if (selection.isNull()) {
305 				selection.create_rgb(_tile_size.x, _tile_size.y, 32);
306 				selection.display_format_alpha();
307 				selection.fill(selection.map_rgba(0x11, 0xdd, 0x11, 0x80));
308 			}
309 			v2<int> base(map_pos.x, map_pos.y);
310 			v2<int> sel1 = _selection1, sel2 = _selection2;
311 			if (sel1.x > sel2.x)
312 				math::exchange(sel1.x, sel2.x);
313 			if (sel1.y > sel2.y)
314 				math::exchange(sel1.y, sel2.y);
315 
316 			v2<int> t1 = v2<int>(sel1.x, sel1.y) / _tile_size * _tile_size - base;
317 			v2<int> t2 = v2<int>(sel2.x, sel2.y) / _tile_size * _tile_size - base;
318 			//LOG_DEBUG(("%d,%d -> %d,%d", t1.x, t1.y, t2.x, t2.y));
319 			for(int y = t1.y; y <= t2.y; y += _tile_size.y)
320 				for(int x = t1.x; x <= t2.x; x += _tile_size.x) {
321 					surface.blit(selection, x, y);
322 				}
323 		}
324 	}
325 
326 	if ((show_layer_name || highlight_layer) && !_layer_name.empty()) {
327 		//int w = _small_font->render(NULL, 0, 0, _layer_name);
328 		_small_font->render(surface, 8, 8, _layer_name);
329 	}
330 
331 	if (_brush == NULL && _highlight_object) {
332 		static const sdlx::Surface *hl;
333 		if (hl == NULL)
334 			hl = ResourceManager->load_surface("object.png");
335 
336 		v2<float> pos;
337 		_highlight_object->get_center_position(pos);
338 		surface.blit(*hl,
339 			(int)(pos.x - map_pos.x - hl->get_width() / 2),
340 			(int)(pos.y - map_pos.y - hl->get_height() / 2)
341 		);
342 	}
343 
344 	Container::render(surface, 0, 0);
345 }
346 
347 #include "mrt/lang.h"
348 
init(int argc,char * argv[])349 void Editor::init(int argc, char *argv[]) {
350 	std::string config_path = mrt::Directory::get_app_dir("Battle Tanks", "btanks") + "/";
351 	Config->load(config_path + "bt.xml");
352 
353 	std::string lang; //default
354 
355 	for(int i = 1; i < argc; ++i) {
356 		if (strncmp(argv[i], "--lang=", 7) == 0) { lang = argv[i] + 7; }
357 	}
358 
359 	if (lang.empty()) {
360 		if (Config->has("engine.language")) {
361 			Config->get("engine.language", lang, std::string());
362 		}
363 
364 		if (lang.empty())
365 			lang = mrt::get_lang_code();
366 	}
367 
368 	I18n->load(lang);
369 	Window->init(argc, argv);
370 	sdlx::Rect window_size = Window->get_size();
371 
372 	LOG_DEBUG(("initializing hud..."));
373 	_hud = new Hud(window_size.w, window_size.h);
374 
375 	LOG_DEBUG(("installing callbacks..."));
376 
377 	on_key_slot.assign(this, &Editor::onKeySignal, Window->key_signal);
378 	on_mouse_slot.assign(this, &Editor::onMouseSignal, Window->mouse_signal);
379 	on_mouse_motion_slot.assign(this, &Editor::onMouseMotionSignal, Window->mouse_motion_signal);
380 	on_event_slot.assign(this, &Editor::onEvent, Window->event_signal);
381 
382 	reset_slot.assign(this, &Editor::resetLoadingBar, Map->reset_progress);
383 	notify_slot.assign(this, &Editor::notifyLoadingBar, Map->notify_progress);
384 
385 	reset_slot.assign(this, &Editor::resetLoadingBar, ResourceManager->reset_progress);
386 	notify_slot.assign(this, &Editor::notifyLoadingBar, ResourceManager->notify_progress);
387 
388 	on_tick_slot.assign(this, &Editor::onTick, Window->tick_signal);
389 
390 	std::vector<std::pair<std::string, std::string> > files;
391 	Finder->findAll(files, "resources.xml");
392 
393 	ResourceManager->init(files);
394 
395 	Control * c = _map_picker = new OpenMapDialog;
396 	int cw, ch;
397 	c->get_size(cw, ch);
398 	add((window_size.w - cw) / 2, (window_size.h - ch) / 2, c);
399 
400 	c = _tileset_dialog = new TilesetDialog(window_size.w, window_size.h);
401 	add(0, 0, c);
402 	c->hide();
403 
404 	c = _layers_dialog = new LayerListDialog(window_size.w, window_size.h);
405 	c->get_size(cw, ch);
406 	add((window_size.w - cw), 0, c);
407 	c->hide();
408 
409 	c = _add_object = new AddObjectDialog(512, 384);
410 	c->get_size(cw, ch);
411 	add((window_size.w - cw) / 2, (window_size.h - ch) / 2, c);
412 	c->hide();
413 
414 	c = _morph_dialog = new MorphDialog(512, 384);
415 	c->get_size(cw, ch);
416 	add((window_size.w - cw) / 2, (window_size.h - ch) / 2, c);
417 	c->hide();
418 
419 	RTConfig->editor_mode = true;
420 
421 	_small_font = ResourceManager->loadFont("small", true);
422 
423 	_object_properties = new ObjectPropertiesDialog(240);
424 	add(0, 0, _object_properties);
425 	_object_properties->hide();
426 
427 	_resize_map = new ResizeDialog();
428 	_resize_map->get_size(cw, ch);
429 	add((window_size.w - cw) / 2, (window_size.h - ch) / 2, _resize_map);
430 	_resize_map->hide();
431 }
432 
run()433 void Editor::run() {
434 	LOG_DEBUG(("entering main loop"));
435 	Window->run();
436 	LOG_DEBUG(("exiting main loop"));
437 }
438 
deinit()439 void Editor::deinit() {
440 
441 	delete _hud;
442 	_hud = NULL;
443 
444 	ResourceManager->clear();
445 	Window->deinit();
446 
447 	TRY {
448 		Config->save();
449 	} CATCH("saving config", {});
450 }
451 
onKey(const SDL_keysym sym)452 bool Editor::onKey(const SDL_keysym sym) {
453 	if (Container::onKey(sym))
454 		return true;
455 
456 	int dir = 0;
457 	switch(sym.sym) {
458 	case SDLK_x:
459 	case SDLK_c:
460 		if ((sym.mod & KMOD_CTRL) == 0)
461 			break;
462 		{
463 			if (_brush != NULL || _selecting || _selection1.quick_distance(_selection2) < 1024)
464 				return true;
465 
466 			Layer * l = Map->getLayer(_current_layer_z);
467 			if (!l->isVisible())
468 				return true;
469 
470 			v2<int> t1 = v2<int>(_selection1.x, _selection1.y) / _tile_size;
471 			v2<int> t2 = v2<int>(_selection2.x, _selection2.y) / _tile_size;
472 			//LOG_DEBUG(("%d,%d -> %d,%d", t1.x, t1.y, t2.x, t2.y));
473 
474 			std::vector<int> tiles;
475 			bool empty = true;
476 			for(int y = t1.y; y <= t2.y; ++y)
477 				for(int x = t1.x; x <= t2.x; ++x) {
478 					int tid = l->get(x, y);
479 					//LOG_DEBUG(("tid: %d", tid));
480 					tiles.push_back(tid);
481 					if (tid)
482 						empty = false;
483 				}
484 			if (empty)
485 				return true;
486 
487 			_brush = new Brush(_tile_size, tiles);
488 			_brush->size = t2 - t1 + 1;
489 
490 			bool cut = sym.sym == SDLK_x;
491 			_selection1.clear();
492 			_selection2.clear();
493 			LOG_DEBUG(("copypastecut: %s", cut?"cut":"copy"));
494 			if (cut) {
495 				{
496 					Command cmd(l);
497 					addCommand(cmd);
498 				}
499 				for(int y = t1.y; y <= t2.y; ++y)
500 					for(int x = t1.x; x <= t2.x; ++x) {
501 						currentCommand().setTile(x, y, 0);
502 				}
503 				currentCommand().exec();
504 			}
505 		}
506 		return true;
507 
508 	case SDLK_q:
509 		if (sym.mod & KMOD_ALT) {
510 			Window->stop();
511 			return true;
512 		}
513 		break;
514 	case SDLK_ESCAPE:
515 		if (_brush) {
516 			delete _brush;
517 			_brush = NULL;
518 		}
519 		return true;
520 
521 	case SDLK_INSERT:
522 	case SDLK_i:
523 		if (Map->loaded()) {
524 			SDL_GetMouseState(&mouse_pos.x, &mouse_pos.y);
525 			_add_object->hide(false);
526 		}
527 		return true;
528 
529 	case SDLK_z:
530 		if (!Map->loaded())
531 			return true;
532 
533 		if ((sym.mod & KMOD_CTRL) != 0) {
534 			if (sym.mod & KMOD_SHIFT)
535 				redo();
536 			else
537 				undo();
538 			return true;
539 		}
540 		break;
541 	case SDLK_r:
542 		if (!Map->loaded())
543 			return true;
544 
545 		if ((sym.mod & KMOD_ALT) != 0) {
546 			_resize_map->show();
547 			return true;
548 		}
549 
550 		if ((sym.mod & KMOD_CTRL) != 0) {
551 			redo();
552 			return true;
553 		}
554 		break;
555 	case SDLK_b: {
556 		if (!Map->loaded())
557 			return true;
558 
559 		BaseBrush *b = _tileset_dialog->getBrush();
560 		if (b->size.is0()) {
561 			delete b;
562 			return true;
563 		}
564 		delete _brush;
565 		_brush = b;
566 		return true;
567 		}
568 	case SDLK_g: {
569 		int mx, my;
570 		SDL_GetMouseState(&mx, &my);
571 		v2<int> window_pos(mx, my);
572 		v2<int> base(map_pos.x, map_pos.y);
573 		v2<int> pixel_pos = base + window_pos;
574 		v2<int> tile_pos = pixel_pos / _tile_size;
575 		//mrt::format_string("%d,%d", tile_pos.x, tile_pos.y).c_str());
576 		_layer_name = mrt::format_string("grid_position: %d,%d, pixel position: %d,%d", tile_pos.x, tile_pos.y, pixel_pos.x, pixel_pos.y);
577 		_layer_name_invisible.reset();
578 
579 		return true;
580 	}
581 	case SDLK_f: {
582 		//LOG_DEBUG(("f"));
583 		if (!Map->loaded())
584 			return true;
585 
586 		Layer * l = Map->getLayer(_current_layer_z);
587 		if (!l->isVisible())
588 			return true;
589 
590 		Brush *p_brush = dynamic_cast<Brush *>(_brush);
591 		if (p_brush) {
592 			//LOG_DEBUG(("tile pos: %d %d", x / _tile_size.x, y / _tile_size.y));
593 			//_last_tile_painted = v2<int>((map_pos.x + x)/ _tile_size.x, (map_pos.y + y) / _tile_size.y);
594 			int x, y;
595 			SDL_GetMouseState(&x, &y);
596 
597 			v2<int> pos((map_pos.x + x)/ _tile_size.x, (map_pos.y + y) / _tile_size.y);
598 			{
599 				Command cmd(l);
600 				addCommand(cmd);
601 			}
602 			FillerBrush brush(*p_brush, Map->get_size() / Map->getTileSize());
603 			brush.exec(currentCommand(), pos.x, pos.y );
604 			currentCommand().exec();
605 		} else
606 			LOG_DEBUG(("_brush is not Brush. skip fill"));
607 		return true;
608 	}
609 	case SDLK_e:
610 		if (!Map->loaded())
611 			return true;
612 
613 		LOG_DEBUG(("eraser"));
614 		delete _brush;
615 		_brush = new Eraser(_tile_size);
616 		return true;
617 	case SDLK_m:
618 		if (!Map->loaded())
619 			return true;
620 
621 		_morph_dialog->hide(!_morph_dialog->hidden());
622 		return true;
623 	case SDLK_o:
624 		if (sym.mod & KMOD_ALT) {
625 			_map_picker->hide(false);
626 		} else {
627 			_render_objects = !_render_objects;
628 		}
629 		return true;
630 	case SDLK_s:
631 		if (sym.mod & KMOD_ALT && Map->loaded()) {
632 			std::string result;
633 			Map->generateXML(result);
634 			//LOG_DEBUG(("map returned:\n%s", result.c_str()));
635 			try {
636 				mrt::Directory dir;
637 				dir.create(_map_base + "/maps", true);
638 			} catch(...) {}
639 			mrt::File f;
640 			std::string fname = _map_base + "/maps/" + _map_file + ".tmx";
641 			f.open(fname, "wb");
642 			LOG_DEBUG(("saving map to %s...", fname.c_str()));
643 			f.write_all(result);
644 			f.close();
645 			return true;
646 		} break;
647 	case SDLK_TAB:
648 		if (!Map->loaded())
649 			return true;
650 
651 		_tileset_dialog->hide(false);
652 		return true;
653 		//if (!_tileset_dialog->hidden() || !_map_picker->hidden() || !Map->loaded())
654 		//	return false;
655 		//_layers_dialog->hide(false);
656 	case SDLK_LSHIFT:
657 	case SDLK_RSHIFT:
658 		_layer_invisible.reset();
659 		return true;
660 	case SDLK_DELETE:
661 		if (_highlight_object) {
662 			Command cmd(Command::DeleteObject, _highlight_object);
663 			addCommand(cmd);
664 			currentCommand().exec();
665 
666 			_highlight_object = NULL;
667 		}
668 		return true;
669 
670 	case SDLK_RIGHTBRACKET:
671 		dir = -2;
672 	case SDLK_LEFTBRACKET:
673 		++dir;
674 		if (_highlight_object == NULL)
675 			return false;
676 		{
677 			int dirs = _highlight_object->get_directions_number();
678 			if (dirs != 8 && dirs != 16)
679 				return false;
680 
681 			Command cmd(Command::RotateObject, _highlight_object, dir);
682 			addCommand(cmd);
683 			currentCommand().exec();
684 			displayStatusMessage(_highlight_object);
685 		}
686 		return true;
687 	case SDLK_KP_MINUS:
688 	case SDLK_MINUS:
689 		dir = -20;
690 	case SDLK_PLUS:
691 	case SDLK_KP_PLUS:
692 		dir += 10;
693 		if (_highlight_object == NULL)
694 			return false;
695 		{
696 			Command cmd(Command::ChangeObjectProperties, _highlight_object, _highlight_object->get_z() + dir, Variants(_highlight_object->get_variants()));
697 			addCommand(cmd);
698 			currentCommand().exec();
699 			displayStatusMessage(_highlight_object);
700 		}
701 		return true;
702 
703 	default:
704 		return false;
705 	}
706 	return false;
707 }
708 
displayStatusMessage(const Object * object)709 void Editor::displayStatusMessage(const Object *object) {
710 	std::string prop;
711 	try {
712 		GameItem &item = GameMonitor->find(_highlight_object);
713 		prop = " (" + item.property + ")";
714 	} catch(...) {}
715 	_layer_name = mrt::format_string("%s:%s%s, z: %d, dir: %d%s",
716 		object->registered_name.c_str(), object->animation.c_str(), object->get_variants().dump().c_str(), object->get_z(), object->get_direction(), prop.c_str());
717 	_layer_name_invisible.reset();
718 }
719 
onMouse(const int button,const bool pressed,const int x,const int y)720 bool Editor::onMouse(const int button, const bool pressed, const int x, const int y) {
721 	if (Container::onMouse(button, pressed, x, y))
722 		return true;
723 
724 	if (!Map->loaded()) {
725 		return false;
726 	}
727 
728 	static const Uint8 *keys = SDL_GetKeyState(0);
729 	if (keys[SDLK_LSHIFT] != 0) {
730 
731 		if (button == SDL_BUTTON_WHEELUP) {
732 			if (pressed)
733 				_layers_dialog->up();
734 		} else if (button == SDL_BUTTON_WHEELDOWN) {
735 			if (pressed)
736 				_layers_dialog->down();
737 		}
738 
739 		return true;
740 	} else if (keys[SDLK_SPACE] != 0) {
741 		if (button == SDL_BUTTON_RIGHT) {
742 			if (!pressed)
743 				return true;
744 
745 			const sdlx::Rect window_size = Window->get_size();
746 
747 			map_pos.x += x - window_size.w / 2;
748 			map_pos.y += y - window_size.h / 2;
749 
750 		}
751 		return true;
752 	} else {
753 		if (button == SDL_BUTTON_WHEELUP) {
754 			if (pressed)
755 				map_pos.y -= 128;
756 			return true;
757 		} else if (button == SDL_BUTTON_WHEELDOWN) {
758 			if (pressed)
759 				map_pos.y += 128;
760 			return true;
761 		}
762 
763 
764 	}
765 
766 	if (pressed && button == SDL_BUTTON_LEFT && _brush) {
767 		if (_layers_dialog->empty()) {
768 			_layer_name = "add new layer with shift-n";
769 			_layer_name_invisible.reset();
770 			return true;
771 		}
772 		//LOG_DEBUG(("click"));
773 		ObjectBrush *object_brush = dynamic_cast<ObjectBrush *>(_brush);
774 		if (object_brush != NULL) {
775 			Command cmd(object_brush->classname, object_brush->animation,
776 				v2<int>(map_pos.x + x - _brush->size.x / 2, map_pos.y + y - _brush->size.y / 2), object_brush->z);
777 			addCommand(cmd);
778 			currentCommand().exec();
779 			return true;
780 		}
781 		Layer * l = Map->getLayer(_current_layer_z);
782 		if (l->isVisible()) {
783 			//LOG_DEBUG(("tile pos: %d %d", x / _tile_size.x, y / _tile_size.y));
784 			_last_tile_painted = v2<int>((map_pos.x + x)/ _tile_size.x, (map_pos.y + y) / _tile_size.y);
785 			Command cmd(l);
786 			addCommand(cmd);
787 			_brush->exec(currentCommand(), _last_tile_painted.x, _last_tile_painted.y );
788 		}
789 		return true;
790 	}
791 
792 	if (button == SDL_BUTTON_LEFT && pressed && _highlight_object) {
793 		_dragging = true;
794 		LOG_DEBUG(("dragging object..."));
795 		Command cmd(Command::MoveObject, _highlight_object);
796 		addCommand(cmd);
797 
798 		Object *object = currentCommand().getObject();
799 		assert(object != NULL);
800 		v2<int> pos;
801 		object->get_position(pos);
802 		currentCommand().save(pos.x, pos.y);
803 		return true;
804 	}
805 
806 	if (button == SDL_BUTTON_LEFT && !pressed && _dragging) {
807 		_dragging = false;
808 		LOG_DEBUG(("dropping object..."));
809 		v2<int> pos;
810 		currentCommand().getObject()->get_position(pos);
811 		currentCommand().move(pos.x, pos.y);
812 		currentCommand().exec();
813 		return true;
814 	}
815 
816 	if (button == SDL_BUTTON_RIGHT && !pressed && _highlight_object) {
817 		_object_properties->set_base(x, y);
818 		std::set<std::string> variants;
819 		_add_object->get_variants(variants, _highlight_object->registered_name);
820 		_object_properties->show(_highlight_object, variants);
821 		return true;
822 	}
823 
824 	if (button == SDL_BUTTON_RIGHT && pressed && !_selecting && _highlight_object == NULL) {
825 		_selecting = true;
826 		LOG_DEBUG(("start selection"));
827 		_selection1 = _selection2 = v2<int>(x + map_pos.x, y + map_pos.y);
828 		return true;
829 	}
830 
831 	if (button == SDL_BUTTON_RIGHT && !pressed && _selecting) {
832 		_selecting = false;
833 		if (_selection1.x > _selection2.x)
834 			math::exchange(_selection1.x, _selection2.x);
835 		if (_selection1.y > _selection2.y)
836 			math::exchange(_selection1.y, _selection2.y);
837 		v2<int> size = _selection2 - _selection1 + 1;
838 		LOG_DEBUG(("selection: %d,%d %dx%d", _selection1.x, _selection1.y, size.x, size.y));
839 		return true;
840 	}
841 
842 	return false;
843 }
844 
845 
onMouseMotion(const int state,const int x,const int y,const int xrel,const int yrel)846 bool Editor::onMouseMotion(const int state, const int x, const int y, const int xrel, const int yrel) {
847 	if (Container::onMouseMotion(state, x, y, xrel, yrel))
848 		return true;
849 	const bool lmb = (state & SDL_BUTTON_LMASK) != 0;
850 
851 	if (_render_objects && _brush == NULL && state == 0 && _add_object->hidden()) {
852 		//LOG_DEBUG(("mouse %d %d", x, y));
853 		_highlight_object = World->getObjectByXY((int)map_pos.x + x, (int)map_pos.y + y);
854 		if (_highlight_object) {
855 			displayStatusMessage(_highlight_object);
856 		}
857 	}
858 
859 	static const Uint8 *keys = SDL_GetKeyState(0);
860 
861 	if (lmb && _brush != NULL && !_brush->size.is0() && keys[SDLK_SPACE] == 0 && !undo_queue.empty()) { //no space
862 		v2<int> tile_pos ((map_pos.x + x)/ _tile_size.x, (map_pos.y + y) / _tile_size.y);
863 		v2<int> dpos = tile_pos - _last_tile_painted;
864 		v2<int> shift (math::abs(dpos.x), math::abs(dpos.y));
865 		if (shift.x > shift.y)
866 			shift.y = 0;
867 		else
868 			shift.x = 0;
869 
870 		if (shift.x >= _brush->size.x || shift.y >= _brush->size.y) {
871 			dpos.x = math::sign(dpos.x);
872 			dpos.y = math::sign(dpos.y);
873 			do {
874 				_last_tile_painted += shift * dpos;
875 				//LOG_DEBUG(("last tile: %d %d", _last_tile_painted.x, _last_tile_painted.y ));
876 				_brush->exec(currentCommand(), _last_tile_painted.x, _last_tile_painted.y );
877 				shift -= _brush->size;
878 			} while(shift.x >= 0 && shift.y >= 0);
879 			//LOG_DEBUG(("draw line: tile_pos: %d %d", tile_pos.x, tile_pos.y));
880 		}
881 	}
882 	if (_dragging) {
883 		v2<int> pos;
884 		currentCommand().getObject()->get_position(pos);
885 		pos.x += xrel; pos.y += yrel;
886 
887 		World->move(currentCommand().getObject(), pos.x, pos.y);
888 	}
889 	if (_selecting) {
890 		_selection2 = v2<int>(map_pos.x + x, map_pos.y + y);
891 	}
892 
893 	if (keys[SDLK_SPACE] == 0 || !lmb || !Map->loaded())
894 		return false;
895 
896 	map_pos.x -= xrel;
897 	map_pos.y -= yrel;
898 
899 	return true;
900 }
901 
resetLoadingBar(const int total)902 void Editor::resetLoadingBar(const int total) {
903 	_loading_bar_now = 0;
904 	_loading_bar_total = total;
905 }
906 
notifyLoadingBar(const int progress,const char * what)907 void Editor::notifyLoadingBar(const int progress, const char *what) {
908 	GET_CONFIG_VALUE("hud.disable-loading-screen", bool, disable_bar, false);
909 	if (disable_bar)
910 		return;
911 
912 	float old_progress = 1.0 * _loading_bar_now / _loading_bar_total;
913 	_loading_bar_now += progress;
914 
915 	if (_hud->renderLoadingBar(Window->get_surface(), old_progress, 1.0 * _loading_bar_now / _loading_bar_total, NULL, false)) {
916 		Window->flip();
917 		Window->get_surface().fill(Window->get_surface().map_rgb(255, 255, 255));
918 	}
919 }
920 
updateLayers()921 void Editor::updateLayers() {
922 	if (!_layers_dialog->empty()) {
923 		const LayerItem *li = _layers_dialog->getCurrentItem();
924 		_current_layer_z = li->z;
925 		_layer_name = li->layer->name;
926 		_layer_name_invisible.reset();
927 		_layer_invisible.reset();
928 	}
929 }
930 
931 
onKeySignal(const SDL_keysym sym,const bool pressed)932 bool Editor::onKeySignal(const SDL_keysym sym, const bool pressed) {
933 	if (pressed && onKey(sym))
934 		return true;
935 	return false;
936 }
937 
onMouseSignal(const int button,const bool pressed,const int x,const int y)938 bool Editor::onMouseSignal(const int button, const bool pressed, const int x, const int y) {
939 	return onMouse(button, pressed, x, y);
940 }
941 
onMouseMotionSignal(const int state,const int x,const int y,const int xrel,const int yrel)942 bool Editor::onMouseMotionSignal(const int state, const int x, const int y, const int xrel, const int yrel) {
943 	return onMouseMotion(state, x, y, xrel, yrel);
944 }
945 
onEvent(const SDL_Event & event)946 void Editor::onEvent(const SDL_Event &event) {
947 	if (event.type == SDL_QUIT)
948 		Window->stop();
949 }
950 
951 
952 #ifdef _WINDOWS
953 #	include "sdlx/SDL_main.h"
954 #	define WIN32_LEAN_AND_MEAN
955 #	include <windows.h>
956 #endif
957 
958 #include "sdlx/system.h"
959 
960 #ifdef __cplusplus
961 extern "C"
962 #endif
main(int argc,char * argv[])963 int main(int argc, char *argv[]) {
964 try {
965 	LOG_DEBUG(("bt editor"));
966 	Game->loadPlugins();
967 
968 	Editor editor;
969 	editor.init(argc, argv);
970 	editor.run();
971 	editor.deinit();
972 #ifdef _WINDOWS
973 	} catch(const std::exception &e) {
974 		sdlx::System::deinit();
975 		LOG_ERROR(("main:%s", e.what()));
976 		MessageBox(NULL, e.what(), "Error", MB_OK | MB_ICONERROR | MB_TASKMODAL);
977 		return 1;
978 	}
979 #else
980 	} CATCH("main", {
981 		sdlx::System::deinit();
982 		return 1;
983 	})
984 #endif
985 
986 	sdlx::System::deinit();
987 	return 0;
988 }
989 
990 
991 #include "mrt/directory.h"
992 #include <string>
993 
994 #ifdef _WINDOWS
995 extern "C" {
996 #ifdef _WIN32_WCE
997 int WINAPI SDLWinMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int sw);
WinMain(HINSTANCE hInst,HINSTANCE hPrev,LPWSTR szCmdLine,int sw)998 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int sw)
999 #else
1000 int WINAPI SDLWinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw);
1001 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
1002 #endif
1003 {
1004 	std::string logpath = mrt::format_string("SDL_LOG_PATH=%s", mrt::Directory::get_app_dir("Battle Tanks", "btanks").c_str());
1005 	_putenv(_strdup(logpath.c_str()));
1006 	return SDLWinMain(hInst, hPrev, szCmdLine, sw);
1007 }
1008 }
1009 #endif
1010