1 /*
2  *  Copyright (C) 2000-2013  The Exult Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22 
23 #include <iostream>
24 #include <sstream>
25 #include <algorithm>
26 #include <string>
27 #include <iomanip>
28 #include <map>
29 using std::setw;
30 using std::setfill;
31 
32 #include "SDL_mouse.h"
33 #include "cheat.h"
34 #include "exult.h"
35 #include "gamewin.h"
36 #include "gameclk.h"
37 #include "gamemap.h"
38 #include "party.h"
39 #include "Configuration.h"
40 #include "game.h"
41 #include "actors.h"
42 #include "mouse.h"
43 #include "browser.h"
44 #include "soundtest.h"
45 #include "cheat_screen.h"
46 #include "Gump_manager.h"
47 #include "Gump.h"
48 #include "drag.h"
49 #include "effects.h"
50 #include "chunks.h"
51 #include "objiter.h"
52 #include "miscinf.h"
53 #include "touchui.h"
54 
55 #ifdef USE_EXULTSTUDIO  /* Only needed for exult studio. */
56 #include "server.h"
57 #include "servemsg.h"
58 #include "ignore_unused_variable_warning.h"
59 
60 #ifdef _WIN32
61 #ifndef WIN32_LEAN_AND_MEAN
62 #define WIN32_LEAN_AND_MEAN
63 #endif
64 #include <windows.h>
65 #endif // _WIN32
66 
67 #endif //USE_EXULTSTUDIO
68 
69 using std::cout;
70 using std::endl;
71 
72 Cheat cheat;
73 
74 
Cheat()75 Cheat::Cheat() {
76 	enabled = false;
77 
78 	god_mode = false;
79 	wizard_mode = false;
80 	map_editor = false;
81 	tile_grid = false;
82 	edit_mode = move;
83 	edit_lift = 0;
84 	edit_shape = edit_frame = edit_chunknum = 0;
85 	infravision = false;
86 	pickpocket = false;
87 	grab_actor = true;
88 	npc_numbers = false;
89 	hack_mover = false;
90 
91 	browser = nullptr;
92 	tester = nullptr;
93 	cscreen = nullptr;
94 	chunksel_left = chunksel_top = c_num_chunks;
95 	chunksel_right = chunksel_bottom = -1;
96 }
97 
~Cheat()98 Cheat::~Cheat() {
99 	delete browser;
100 	delete tester;
101 	delete cscreen;
102 }
103 
init()104 void Cheat::init() {
105 	std::string cheating;
106 	config->value("config/gameplay/cheat", cheating, "no");
107 	enabled = cheating == "yes";
108 }
109 
finish_init()110 void Cheat::finish_init() {
111 
112 	browser = new ShapeBrowser();
113 	tester = new SoundTester();
114 	cscreen = new CheatScreen();
115 
116 	if (enabled)
117 		cout << "Cheats enabled." << endl;
118 }
119 
120 
set_enabled(bool en)121 void Cheat::set_enabled(bool en) {
122 	enabled = en;
123 	std::string cheating;
124 	if (enabled)
125 		cheating = "yes";
126 	else
127 		cheating = "no";
128 	config->set("config/gameplay/cheat", cheating, true);
129 }
130 
toggle_god()131 void Cheat::toggle_god() {
132 	if (!enabled) return;
133 
134 	god_mode = !god_mode;
135 	if (god_mode) {
136 		eman->center_text("God Mode Enabled");
137 		Actor *party[9];        // Set attack mode to 'nearest'.
138 		int cnt = gwin->get_party(party, 1);
139 		for (int i = 0; i < cnt; i++)
140 			party[i]->set_attack_mode(Actor::nearest);
141 	} else
142 		eman->center_text("God Mode Disabled");
143 }
144 
toggle_wizard()145 void Cheat::toggle_wizard() {
146 	if (!enabled) return;
147 
148 	wizard_mode = !wizard_mode;
149 	if (wizard_mode)
150 		eman->center_text("Archwizard Mode Enabled");
151 	else
152 		eman->center_text("Archwizard Mode Disabled");
153 }
154 
toggle_map_editor()155 void Cheat::toggle_map_editor() {
156 	if (!enabled) return;
157 
158 	map_editor = !map_editor;
159 	if (map_editor) {
160 		eman->center_text("Map Editor Mode Enabled");
161 		// Stop time.
162 		gwin->set_time_stopped(-1);
163 #ifdef USE_EXULTSTUDIO          /* Launch ExultStudio! */
164 		if (!gwin->paint_eggs) { // Show eggs too.
165 			gwin->paint_eggs = true;
166 			gwin->paint();
167 		}
168 		if (client_socket < 0 &&
169 		        !gwin->get_win()->is_fullscreen()) {
170 			std::string cmnd("exult_studio -x ");     // Set up command.
171 #ifdef _WIN32
172 			if (get_system_path("<HOME>") == ".")   // portable
173 				cmnd += " -p ";
174 #endif
175 			std::string data_path;
176 #ifdef MACOSX
177 			// first try whether the Exult_Studio app bundle is installed
178 			// and use its data folder
179 			std::string app_path("/Applications/Exult_Studio.app");
180 			if (U7exists(app_path)) {
181 				data_path = app_path;
182 				cmnd = "open ";
183 				cmnd += app_path;
184 				cmnd += " --args -x ";
185 				data_path += "/Contents/Resources/data/";
186 				std::cout << "cmnd Path   : " << cmnd << std::endl;
187 			}
188 			else if (is_system_path_defined("<BUNDLE>"))
189 				data_path = get_system_path("<BUNDLE>");
190 			else
191 #endif
192 				data_path = get_system_path("<DATA>");
193 			if (data_path.find(' ') != std::string::npos)
194 				data_path = "\"" + data_path + "\"";
195 			cmnd += data_path;// Path to where .glade file should be.
196 			cmnd += " -g ";   // Now want game name.
197 			cmnd += Game::get_gametitle();
198 			// Now want mod name.
199 			std::string modnamestr = Game::get_modtitle();
200 			if (!modnamestr.empty()) {
201 				cmnd += " -m ";
202 				cmnd += modnamestr;
203 			}
204 			std::string alt_cfg = get_system_path("<alt_cfg>");
205 			if (alt_cfg != "<alt_cfg>") {
206 				cmnd += " -c ";
207 				cmnd += alt_cfg;
208 			}
209 			cmnd += " &";
210 			cout << "Executing: " << cmnd << endl;
211 #ifndef _WIN32
212 			int ret = system(cmnd.c_str());
213 			if (ret == 127 || ret == -1)
214 				cout << "Couldn't run Exult Studio" << endl;
215 #else
216 			PROCESS_INFORMATION pi;
217 			STARTUPINFO     si;
218 
219 			std::memset(&si, 0, sizeof(si));
220 			si.cb = sizeof(si);
221 
222 			int ret = CreateProcess(nullptr, &cmnd[0], nullptr, nullptr,
223 			                        FALSE, 0,
224 			                        nullptr, nullptr, &si, &pi);
225 			if (!ret) cout << "Couldn't run Exult Studio" << endl;
226 #endif
227 		}
228 #endif
229 	} else {
230 		clear_selected();   // Selection goes away.
231 		eman->center_text("Map Editor Mode Disabled");
232 		// Stop time-stop.
233 		gwin->set_time_stopped(0);
234 	}
235 }
236 
toggle_tile_grid()237 void Cheat::toggle_tile_grid() {
238 	if (!enabled) return;
239 	tile_grid = !tile_grid;
240 	gwin->set_all_dirty();
241 }
242 
set_edit_mode(Map_editor_mode md)243 void Cheat::set_edit_mode(Map_editor_mode md) {
244 	edit_mode = md;
245 	if (edit_mode != select_chunks) {
246 		clear_chunksel();
247 		gwin->set_all_dirty();
248 	}
249 }
250 
clear_chunksel()251 void Cheat::clear_chunksel() {
252 	if (chunksel_right >= 0 && chunksel_bottom >= 0) {
253 		int startx = chunksel_left;
254 		int stopx = chunksel_right + 1;
255 		int starty = chunksel_top;
256 		int stopy = chunksel_bottom + 1;
257 		for (int cy = starty; cy != stopy; cy = INCR_CHUNK(cy))
258 			for (int cx = startx; cx != stopx;
259 			        cx = INCR_CHUNK(cx)) {
260 				Map_chunk *chunk = gmap->get_chunk(cx, cy);
261 				chunk->set_selected(false);
262 			}
263 	}
264 	chunksel_left = chunksel_top = c_num_chunks;
265 	chunksel_right = chunksel_bottom = -1;
266 }
267 
add_chunksel(Map_chunk * chunk,bool extend)268 void Cheat::add_chunksel(Map_chunk *chunk, bool extend) {
269 	ignore_unused_variable_warning(extend);
270 	chunk->set_selected(true);
271 	int cx = chunk->get_cx();
272 	int cy = chunk->get_cy();
273 	if (cx < chunksel_left)
274 		chunksel_left = cx;
275 	if (cx > chunksel_right)
276 		chunksel_right = cx;
277 	if (cy < chunksel_top)
278 		chunksel_top = cy;
279 	if (cy > chunksel_bottom)
280 		chunksel_bottom = cy;
281 	// ++++++++LATER:  Handle extend.
282 }
283 
284 /*  Move a given chunk. */
move_chunk(Map_chunk * chunk,int dx,int dy)285 void Cheat::move_chunk(Map_chunk *chunk, int dx, int dy) {
286 	// Figure dest. with wrapping.
287 	int tox = (chunk->get_cx() + dx + c_num_chunks) % c_num_chunks;
288 	int toy = (chunk->get_cy() + dy + c_num_chunks) % c_num_chunks;
289 	Map_chunk *tochunk = gmap->get_chunk(tox, toy);
290 	Game_object_shared_vector tmplist; // Delete objs. in 'tochunk'.
291 
292 	{
293 		// Iterator needs its own scope.
294 		Game_object *obj = nullptr;
295 		Object_iterator toiter(tochunk->get_objects());
296 		while ((obj = toiter.get_next()) != nullptr)
297 			if (!obj->as_npc())
298 				tmplist.push_back(obj->shared_from_this());
299 	}
300 	for (auto& it : tmplist)
301 		it->remove_this();
302 	tmplist.clear();
303 	// Copy terrain into new chunk.
304 	tochunk->set_terrain(chunk->get_terrain());
305 	{
306 		Object_iterator fromiter(chunk->get_objects());
307 		Game_object *obj = nullptr;
308 		while ((obj = fromiter.get_next()) != nullptr)
309 			if (!obj->as_terrain())
310 				tmplist.push_back(obj->shared_from_this());
311 	}
312 	dx *= c_tiles_per_chunk;
313 	dy *= c_tiles_per_chunk;
314 	for (auto& obj : tmplist) {
315 		Tile_coord t = obj->get_tile();
316 		// Got to move objects legally.
317 		obj->move((t.tx + dx + c_num_tiles) % c_num_tiles,
318 		          (t.ty + dy + c_num_tiles) % c_num_tiles, t.tz);
319 	}
320 	// For now, set terrain to #0.
321 	chunk->set_terrain(Game_map::get_terrain(0));
322 	chunk->set_selected(false);
323 	tochunk->set_selected(true);
324 }
325 
326 /*  Move all the selected chunks. */
move_selected_chunks(int dx,int dy)327 void Cheat::move_selected_chunks(int dx, int dy) {
328 	int startx;
329 	int stopx;
330 	int dirx;
331 	int starty;
332 	int stopy;
333 	int diry;
334 
335 	if (dx <= 0) {
336 		startx = chunksel_left;
337 		stopx = chunksel_right + 1;
338 		dirx = 1;
339 	} else {
340 		startx = chunksel_right;
341 		stopx = chunksel_left - 1;
342 		dirx = -1;
343 	}
344 	if (dy <= 0) {
345 		starty = chunksel_top;
346 		stopy = chunksel_bottom + 1;
347 		diry = 1;
348 	} else {
349 		starty = chunksel_bottom;
350 		stopy = chunksel_top - 1;
351 		diry = -1;
352 	}
353 	for (int cy = starty; cy != stopy; cy += diry)
354 		for (int cx = startx; cx != stopx; cx += dirx) {
355 			Map_chunk *chunk = gmap->get_chunk(cx, cy);
356 			if (chunk->is_selected())
357 				move_chunk(chunk, dx, dy);
358 		}
359 	gwin->set_all_dirty();
360 	chunksel_left = (chunksel_left + dx + c_num_chunks) % c_num_chunks;
361 	chunksel_right = (chunksel_right + dx + c_num_chunks) % c_num_chunks;
362 	chunksel_top = (chunksel_top + dy + c_num_chunks) % c_num_chunks;
363 	chunksel_bottom = (chunksel_bottom + dy + c_num_chunks) % c_num_chunks;
364 }
365 
set_edit_lift(int lift)366 void Cheat::set_edit_lift(int lift) {
367 	if (!enabled) return;
368 	edit_lift = lift;
369 	gwin->set_all_dirty();
370 }
371 
set_edit_shape(int sh,int fr)372 void Cheat::set_edit_shape(int sh, int fr) {
373 	edit_shape = sh;
374 	edit_frame = fr;
375 }
376 
toggle_infravision()377 void Cheat::toggle_infravision() {
378 	if (!enabled) return;
379 
380 	infravision = !infravision;
381 	if (infravision)
382 		eman->center_text("Infravision Enabled");
383 	else
384 		eman->center_text("Infravision Disabled");
385 	gclock->set_palette();
386 }
387 
toggle_pickpocket()388 void Cheat::toggle_pickpocket() {
389 	if (!enabled) return;
390 
391 	pickpocket = !pickpocket;
392 	if (pickpocket) {
393 		eman->center_text("Pick Pocket Enabled");
394 		gwin->get_pal()->set(0);
395 	} else
396 		eman->center_text("Pick Pocket Disabled");
397 }
398 
toggle_hack_mover()399 void Cheat::toggle_hack_mover() {
400 	if (!enabled) return;
401 
402 	hack_mover = !hack_mover;
403 	if (hack_mover) {
404 		eman->center_text("Hack mover Enabled");
405 	} else {
406 		eman->center_text("Hack mover Disabled");
407 	}
408 }
409 
change_gender() const410 void Cheat::change_gender() const {
411 	if (!enabled) return;
412 
413 	if (gwin->get_main_actor()->get_type_flag(Actor::tf_sex)) {
414 		gwin->get_main_actor()->clear_type_flag(Actor::tf_sex);
415 		eman->center_text("Avatar is now male");
416 	} else {
417 		gwin->get_main_actor()->set_type_flag(Actor::tf_sex);
418 		eman->center_text("Avatar is now female");
419 	}
420 	gwin->set_all_dirty();
421 }
422 
toggle_eggs() const423 void Cheat::toggle_eggs() const {
424 	if (!enabled) return;
425 
426 	gwin->paint_eggs = !gwin->paint_eggs;
427 	if (gwin->paint_eggs)
428 		eman->center_text("Eggs display enabled");
429 	else
430 		eman->center_text("Eggs display disabled");
431 	gwin->paint();
432 }
433 
toggle_Petra() const434 void Cheat::toggle_Petra() const {
435 	if (!enabled || (Game::get_game_type() != SERPENT_ISLE)) return;
436 
437 	if (gwin->get_main_actor()->get_flag(Obj_flags::petra))
438 		gwin->get_main_actor()->clear_flag(Obj_flags::petra);
439 	else
440 		gwin->get_main_actor()->set_flag(Obj_flags::petra);
441 	gwin->set_all_dirty();
442 }
443 
toggle_naked() const444 void Cheat::toggle_naked() const {
445 	if (!enabled) return;
446 
447 	if (gwin->get_main_actor()->get_flag(Obj_flags::naked))
448 		gwin->get_main_actor()->clear_flag(Obj_flags::naked);
449 	else
450 		gwin->get_main_actor()->set_flag(Obj_flags::naked);
451 	gwin->set_all_dirty();
452 }
453 
change_skin() const454 void Cheat::change_skin() const {
455 	if (!enabled)
456 		return;
457 
458 	int color = gwin->get_main_actor()->get_skin_color();
459 	bool sex = gwin->get_main_actor()->get_type_flag(Actor::tf_sex);
460 	color = Shapeinfo_lookup::GetNextSkin(color, sex, sman->have_si_shapes());
461 
462 	gwin->get_main_actor()->set_skin_color(color);
463 	gwin->set_all_dirty();
464 }
465 
levelup_party() const466 void Cheat::levelup_party() const {
467 	if (!enabled) return;
468 
469 	Actor *party[9];
470 	bool leveledup = false;
471 
472 	// get party, including Avatar
473 	int cnt = gwin->get_party(party, 1);
474 
475 	for (int i = 0; i < cnt; i++) {
476 		int level = party[i]->get_level();
477 		if (level < 10) {
478 			leveledup = true;
479 			int newexp = 25 * (2 << level); // one level higher
480 			party[i]->set_property(Actor::exp, newexp);
481 		}
482 	}
483 
484 	if (leveledup) {
485 		eman->center_text("Level up!");
486 	} else {
487 		eman->center_text("Maximum level reached");
488 	}
489 }
490 
fake_time_period() const491 void Cheat::fake_time_period() const {
492 	if (!enabled) return;
493 
494 	if (!map_editor) {
495 	    std::ostringstream s;
496 		gwin->get_clock()->fake_next_period();
497 		s << "Game clock incremented to " << gclock->get_hour() << ":"
498 		  	 << setfill('0') << setw(2) << gclock->get_minute();
499 		eman->center_text(s.str().c_str());
500 	}
501 }
502 
dec_skip_lift() const503 void Cheat::dec_skip_lift() const {
504 	if (!enabled) return;
505 
506 	if (gwin->skip_lift == 16)
507 		gwin->skip_lift = 11;
508 	else
509 		gwin->skip_lift--;
510 	if (gwin->skip_lift < 0)    // 0 means 'terrain-editing'.
511 		gwin->skip_lift = 16;
512 #ifdef DEBUG
513 	cout << "Skip_lift = " << gwin->skip_lift << endl;
514 #endif
515 	gwin->paint();
516 }
517 
set_skip_lift(int skip) const518 void Cheat::set_skip_lift(int skip) const {
519 	if (!enabled) return;
520 
521 	if ((skip >= 1 && skip <= 11) || skip == 16)
522 		gwin->skip_lift = skip;
523 #ifdef DEBUG
524 	cout << "Skip_lift = " << gwin->skip_lift << endl;
525 #endif
526 	gwin->paint();
527 }
528 
529 /*
530  *  Tell EStudio whether there's a selection and clipboard.
531  */
send_select_status()532 void Cheat::send_select_status() {
533 #ifdef USE_EXULTSTUDIO
534 	if (client_socket >= 0) {
535 		unsigned char msg[2];
536 		msg[0] = selected.empty() ? 0 : 1;
537 		msg[1] = clipboard.empty() ? 0 : 1;
538 		Exult_server::Send_data(client_socket,
539 		                        Exult_server::select_status, &msg[0], 2);
540 	}
541 #endif
542 }
543 
544 /*
545  *  Add an object to the selected list without checking.
546  */
append_selected(Game_object * obj)547 void Cheat::append_selected(Game_object *obj) {
548 	selected.push_back(obj->shared_from_this());
549 	if (selected.size() == 1)   // First one?
550 		send_select_status();
551 }
552 
553 /*
554  *  Toggle the selection of an object.
555  */
toggle_selected(Game_object * obj)556 void Cheat::toggle_selected(Game_object *obj) {
557 	if (!obj->get_owner())
558 		gwin->add_dirty(obj);
559 	else
560 		gwin->set_all_dirty();
561 	// In list?
562 	for (auto it = selected.begin();
563 	        it != selected.end(); ++it)
564 		if ((*it).get() == obj) {
565 			// Yes, so remove it.
566 			selected.erase(it);
567 			if (selected.empty())   // Last one?
568 				send_select_status();
569 			return;
570 		}
571 	selected.push_back(obj->shared_from_this());    // No, so add it.
572 	if (selected.size() == 1)   // 1st one?
573 		send_select_status();
574 }
575 
576 /*
577  *  Clear out selection.
578  */
clear_selected()579 void Cheat::clear_selected() {
580 	if (selected.empty())
581 		return;
582 	for (auto& obj : selected) {
583 		if (!obj->get_owner())
584 			gwin->add_dirty(obj.get());
585 		else
586 			gwin->set_all_dirty();
587 	}
588 	selected.clear();
589 	send_select_status();
590 }
591 
592 /*
593  *  Delete all selected objects.
594  */
delete_selected()595 void Cheat::delete_selected() {
596 	if (selected.empty())
597 		return;
598 	while (!selected.empty()) {
599 		Game_object_shared obj = selected.back();
600 		selected.pop_back();
601 		if (obj->get_owner())
602 			gwin->add_dirty(obj.get());
603 		else            // In a gump?
604 			gwin->set_all_dirty();
605 		obj->remove_this();
606 	}
607 	send_select_status();
608 }
609 
610 /*
611  *  Move the selected objects by given #tiles.  Objects inside another are
612  *  treated as being at the location of their owner.
613  */
move_selected_objs(int dx,int dy,int dz)614 void Cheat::move_selected_objs(int dx, int dy, int dz) {
615 	if (selected.empty())
616 		return;         // Nothing to do.
617 	std::map<Game_object*, Tile_coord> tiles;  // Store locations here.
618 	int lowz = 1000;
619 	int highz = -1000; // Get min/max lift.
620 	// Remove & store old locations.
621 	for (auto& it : selected) {
622 		Game_object *obj = it.get();
623 		Game_object *owner = obj->get_outermost();
624 		// Get location.  Use owner if inside.
625 		Tile_coord tile = owner->get_tile();
626 		tiles[obj] = tile;
627 		if (obj == owner)   // Not inside?
628 			gwin->add_dirty(obj);
629 		else            // In a gump.  Repaint all for now.
630 			gwin->set_all_dirty();
631 		Game_object_shared keep;
632 		obj->remove_this(&keep);
633 		if (tile.tz < lowz)
634 			lowz = tile.tz;
635 		if (tile.tz > highz)
636 			highz = tile.tz;
637 	}
638 	if (lowz + dz < 0)      // Too low?
639 		dz = -lowz;
640 	if (highz + dz > 255)       // Too high?
641 		dz = 255 - highz;
642 	// Add back in new locations.
643 	for (auto& it : selected) {
644 		Tile_coord tile = tiles[it.get()];
645 		int newtx = (tile.tx + dx + c_num_tiles) % c_num_tiles;
646 		int newty = (tile.ty + dy + c_num_tiles) % c_num_tiles;
647 		int newtz = (tile.tz + dz + 256) % 256;
648 		it->set_invalid();
649 		it->move(newtx, newty, newtz);
650 	}
651 }
652 
653 /*  Move selected objects/chunks. */
move_selected(int dx,int dy,int dz)654 void Cheat::move_selected(int dx, int dy, int dz) {
655 	if (edit_mode == select_chunks)
656 		move_selected_chunks(dx, dy);
657 	else
658 		move_selected_objs(dx, dy, dz);
659 }
660 
is_selected(Game_object * o)661 bool Cheat::is_selected(Game_object *o) {
662 	for (auto& it : selected)
663 		if (o == it.get())
664 			return true;
665 	return false;
666 }
667 
668 /*
669  *  Want lowest, southmost, then eastmost first.
670  */
671 class Clip_compare {
672 public:
operator ()(const Game_object_shared & o1,const Game_object_shared & o2)673 	bool operator()(const Game_object_shared& o1, const Game_object_shared& o2) {
674 		Tile_coord t1 = o1->get_tile();
675 		Tile_coord t2 = o2->get_tile();
676 		if (t1.tz != t2.tz)
677 			return t1.tz < t2.tz;
678 		else if (t1.ty != t2.ty)
679 			return t1.ty > t2.ty;
680 		else return t1.tx >= t2.tx;
681 	}
682 };
683 
684 /*
685  *  Cut/copy.
686  */
cut(bool copy)687 void Cheat::cut(bool copy) {
688 	if (selected.empty())
689 		return;         // Nothing selected.
690 	bool clip_was_empty = clipboard.empty();
691 	// Clear out old clipboard.
692 	clipboard.resize(0);
693 	clipboard.reserve(selected.size());
694 	if (!copy)          // Removing?  Force repaint.
695 		gwin->set_all_dirty();
696 	// Go through selected objects.
697 	for (auto& it : selected) {
698 		Game_object_shared newobj;
699 		Game_object_shared keep;
700 		Game_object *obj = it.get();
701 		Tile_coord t = obj->get_outermost()->get_tile();
702 		if (copy)
703 			// TEST+++++REALLY want a 'clone()'.
704 			newobj = gwin->get_map()->create_ireg_object(
705 			          obj->get_shapenum(), obj->get_framenum());
706 		else {           // Cut:  Remove but don't delete.
707 			newobj = obj->shared_from_this();
708 			obj->remove_this(&keep);
709 		}
710 		// Set pos. & add to list.
711 		newobj->set_shape_pos(t.tx % c_tiles_per_chunk,
712 		                   	  t.ty % c_tiles_per_chunk);
713 		clipboard.push_back(newobj);
714 	}
715 	// Sort.
716 	std::sort(selected.begin(), selected.end(), Clip_compare());
717 	if (!copy)          // Cut?  Remove selection.
718 		clear_selected();   // (This will send status.)
719 	else if (clip_was_empty)    // First clipboard object?
720 		send_select_status();
721 }
722 
723 /*
724  *  Create an object as moveable (IREG) or fixed.
725  *  ++++++++Goes away when we have obj->clone()++++++++++
726  */
727 
Create_object(Game_window * gwin,int shape,int frame)728 static Game_object_shared Create_object(
729     Game_window *gwin,
730     int shape, int frame        // What to create.
731 ) {
732 	const Shape_info &info = ShapeID::get_info(shape);
733 	int sclass = info.get_shape_class();
734 	// Is it an ireg (changeable) obj?
735 	bool ireg = (sclass != Shape_info::unusable &&
736 	             sclass != Shape_info::building);
737 	Game_object_shared newobj;
738 	if (ireg)
739 		newobj = gwin->get_map()->create_ireg_object(info,
740 		         shape, frame, 0, 0, 0);
741 	else
742 		newobj = gwin->get_map()->create_ifix_object(shape, frame);
743 	return newobj;
744 }
745 
746 /*
747  *  Paste selection.
748  */
paste(int mx,int my)749 void Cheat::paste(
750     int mx, int my          // Mouse position.
751 ) {
752 	if (clipboard.empty())
753 		return;         // Nothing there.
754 	// Use lowest/south/east for position.
755 	Tile_coord hot = clipboard[0]->get_tile();
756 	clear_selected();       // Remove old selected.
757 	for (const auto& obj : clipboard) {
758 			Tile_coord t = obj->get_tile();
759 		// Figure spot rel. to hot-spot.
760 		int liftpix = ((t.tz - hot.tz) * c_tilesize) / 2;
761 		int x = mx + (t.tx - hot.tx) * c_tilesize - liftpix;
762 		int y = my + (t.ty - hot.ty) * c_tilesize - liftpix;
763 		// +++++Use clone().
764 		Game_object_shared newobj = Create_object(gwin, obj->get_shapenum(),
765 		                    obj->get_framenum());
766 		Dragging_info drag(newobj);
767 		if (drag.drop(x, y, true))  // (Dels if it fails.)
768 			append_selected(newobj.get());
769 	}
770 	gwin->set_all_dirty();      // Just repaint all.
771 }
772 
773 /*
774  *  Prompt for spot to paste to.
775  */
776 
paste()777 void Cheat::paste() {
778 	if (clipboard.empty())
779 		return;
780 	int x;
781 	int y;       // Allow dragging while here:
782 	if (Get_click(x, y, Mouse::greenselect, nullptr, true))
783 		paste(x, y);
784 }
785 
786 const int border = 2;       // For showing map.
787 const int worldsize = c_tiles_per_chunk * c_num_chunks;
788 
789 /*
790  *  Show 'cheat' map.
791  */
792 class Cheat_map : public Game_singletons, public Paintable {
793 public:
794 	int x, y;           // Where it's painted.
795 	int w, h;
796 	Shape_frame *map = nullptr;
797 	Vga_file *mini = nullptr;         // If "minimaps.vga" is found.
Cheat_map(int mapnum=0)798 	Cheat_map(int mapnum = 0) {
799 		if (U7exists(PATCH_MINIMAPS)) {
800 			mini = new Vga_file(PATCH_MINIMAPS);
801 			if (!(map = mini->get_shape(0, mapnum)))
802 				map = mini->get_shape(0, 0);
803 		} else {
804 			ShapeID mapid(game->get_shape("sprites/cheatmap"),
805 			              1, SF_GAME_FLX);
806 			map = mapid.get_shape();
807 		}
808 		// Get coords. for centered view.
809 		w = map->get_width();
810 		h = map->get_height();
811 		x = (gwin->get_width() - w) / 2 + map->get_xleft();
812 		y = (gwin->get_height() - h) / 2 + map->get_yabove();
813 	}
~Cheat_map()814 	~Cheat_map() override {
815 		delete mini;
816 	}
817 
paint()818 	void paint() override {
819 		sman->paint_shape(x, y, map, true);
820 
821 		// mark current location
822 		int xx;
823 		int yy;
824 		Tile_coord t = gwin->get_main_actor()->get_tile();
825 
826 		xx = ((t.tx * (w - border * 2)) / worldsize);
827 		yy = ((t.ty * (h - border * 2)) / worldsize);
828 
829 		xx += x - map->get_xleft() + border;
830 		yy += y - map->get_yabove() + border;
831 		gwin->get_win()->fill8(50, 1, 5, xx, yy - 2);
832 		gwin->get_win()->fill8(50, 5, 1, xx - 2, yy);
833 	}
834 };
835 
map_teleport() const836 void Cheat::map_teleport() const {
837 	if (!enabled) return;
838 	Gump_manager *gumpman = gwin->get_gump_man();
839 	if (touchui != nullptr) {
840 		touchui->hideGameControls();
841 	}
842 	Cheat_map map(gwin->get_map()->get_num());
843 	int xx;
844 	int yy;
845 	if (!Get_click(xx, yy, Mouse::greenselect, nullptr, false, &map)) {
846 		gwin->paint();
847 		if (touchui != nullptr && !gumpman->gump_mode()) {
848 			touchui->showGameControls();
849 		}
850 		return;
851 	}
852 
853 	xx -= map.x - map.map->get_xleft() + border;
854 	yy -= map.y - map.map->get_yabove() + border;
855 	Tile_coord t;
856 	t.tx = static_cast<int>(((xx + 0.5) * worldsize) / (map.w - 2 * border));
857 	t.ty = static_cast<int>(((yy + 0.5) * worldsize) / (map.h - 2 * border));
858 
859 	// World-wrapping.
860 	t.tx = (t.tx + c_num_tiles) % c_num_tiles;
861 	t.ty = (t.ty + c_num_tiles) % c_num_tiles;
862 	cout << "Teleporting to " << t.tx << "," << t.ty << "!" << endl;
863 	t.tz = 0;
864 	if (!gumpman->gump_mode() && touchui != nullptr) {
865 		touchui->showGameControls();
866 	}
867 	gwin->teleport_party(t);
868 	eman->center_text("Teleport!!!");
869 }
870 
cursor_teleport() const871 void Cheat::cursor_teleport() const {
872 	if (!enabled) return;
873 
874 	int x;
875 	int y;
876 	SDL_GetMouseState(&x, &y);
877 	gwin->get_win()->screen_to_game_hdpi(x, y, gwin->get_fastmouse(), x, y);
878 	Tile_coord t(gwin->get_scrolltx() + x / c_tilesize,
879 	             gwin->get_scrollty() + y / c_tilesize, 0);
880 	t.fixme();
881 	gwin->teleport_party(t);
882 	eman->center_text("Teleport!!!");
883 }
884 
next_map_teleport() const885 void Cheat::next_map_teleport() const {
886 	int curmap = gwin->get_map()->get_num();
887 	int newmap = Find_next_map(curmap + 1, 4);  // Look forwards by 4.
888 	if (newmap == -1) {     // Not found?
889 		// Look from 0.
890 		newmap = Find_next_map(0, curmap);
891 		if (newmap == -1) {
892 			eman->center_text("Map not found");
893 			return;
894 		}
895 	}
896 	gwin->teleport_party(gwin->get_main_actor()->get_tile(), true, newmap);
897 	char msg[80];
898 	sprintf(msg, "To map #%02x", newmap);
899 	eman->center_text(msg);
900 }
901 
create_coins() const902 void Cheat::create_coins() const {
903 	if (!enabled) return;
904 
905 	gwin->get_main_actor()->add_quantity(100, 644);
906 	eman->center_text("Added 100 gold coins");
907 }
908 
create_last_shape() const909 void Cheat::create_last_shape() const {
910 	if (!enabled) return;
911 
912 	int current_shape = 0;
913 	int current_frame = 0;
914 	if (browser->get_shape(current_shape, current_frame)) {
915 		Game_object_shared obj = gwin->get_map()->create_ireg_object(
916 		                       current_shape, current_frame);
917 		obj->set_flag(Obj_flags::okay_to_take);
918 		Tile_coord t =  Map_chunk::find_spot(
919 	                    gwin->get_main_actor()->get_tile(), 4, obj.get(), 2);
920 		if (t.tx != -1) {
921 			obj->move(t);
922 			eman->center_text("Object created");
923 		} else
924 			eman->center_text("No room");
925 	} else
926 		eman->center_text("Can only create from 'shapes.vga'");
927 }
928 
delete_object()929 void Cheat::delete_object() {
930 	if (!enabled) return;
931 
932 	int x;
933 	int y;
934 	SDL_GetMouseState(&x, &y);
935 	gwin->get_win()->screen_to_game_hdpi(x, y, gwin->get_fastmouse(), x, y);
936 
937 	Game_object *obj;
938 	Gump *gump = gwin->get_gump_man()->find_gump(x, y);
939 	if (gump) {
940 		obj = gump->find_object(x, y);
941 	} else {            // Search rest of world.
942 		obj = gwin->find_object(x, y);
943 	}
944 
945 	if (obj) {
946 		clear_selected();   // Unselect all.
947 		obj->remove_this();
948 		eman->center_text("Object deleted");
949 		gwin->paint();
950 	}
951 }
952 
heal_party() const953 void Cheat::heal_party() const {
954 	if (!enabled) return;
955 
956 	int i;  // for MSVC
957 	Party_manager *partyman = gwin->get_party_man();
958 	// NOTE:  dead_party_count decrs. as we
959 	//   resurrect.
960 	int count = partyman->get_dead_count();
961 	int dead[16];           // Save in separate list.
962 	if (count > 16)
963 		count = 16;
964 	for (i = 0; i < count; i++)
965 		dead[i] = partyman->get_dead_member(i);
966 	for (i = 0; i < count; i++) {
967 		int npc_num = dead[i];
968 		Dead_body *body = gwin->get_body(npc_num);
969 		Actor *live_npc = gwin->get_npc(npc_num);
970 		if (body && live_npc) {
971 			Tile_coord avpos = gwin->get_main_actor()->get_tile();
972 			body->move(avpos);
973 			live_npc->resurrect(body);
974 		}
975 	}
976 
977 	// heal everyone
978 	Actor *party[9];
979 	count = gwin->get_party(party, 1);
980 	for (i = 0; i < count; i++) {
981 		if (!party[i]->is_dead()) {
982 			// heal
983 			party[i]->set_property(Actor::health, party[i]->get_property(Actor::strength));
984 			// cure poison
985 			party[i]->clear_flag(Obj_flags::poisoned);
986 			party[i]->clear_flag(Obj_flags::charmed);   // cure charmed
987 			party[i]->clear_flag(Obj_flags::cursed);    // cure cursed
988 			party[i]->clear_flag(Obj_flags::paralyzed); // cure paralysis
989 			party[i]->set_temperature(0);               // reset freezing
990 
991 			// remove hunger  +++++ what is "normal" food level??
992 			party[i]->set_property(Actor::food_level, 30);
993 
994 			// restore mana
995 			if (party[i]->get_effective_prop(Actor::magic) > 0)
996 				party[i]->set_property(Actor::mana, party[i]->get_property(Actor::magic));
997 		}
998 	}
999 
1000 	eman->center_text("Party healed");
1001 	gwin->paint();
1002 }
1003 
shape_browser() const1004 void Cheat::shape_browser() const {
1005 	if (!enabled) return;
1006 
1007 	browser->browse_shapes();
1008 	gwin->paint();
1009 	gclock->reset_palette();
1010 }
1011 
get_browser_shape(int & shape,int & frame) const1012 bool Cheat::get_browser_shape(int &shape, int &frame) const {
1013 	if (!enabled) return false;
1014 
1015 	return browser->get_shape(shape, frame);
1016 }
1017 
sound_tester() const1018 void Cheat::sound_tester() const {
1019 	if (!enabled) return;
1020 
1021 	tester->test_sound();
1022 	gwin->paint();
1023 }
1024 
1025 
cheat_screen() const1026 void Cheat::cheat_screen() const {
1027 	if (!enabled) return;
1028 
1029 	cscreen->show_screen();
1030 	gwin->set_all_dirty();
1031 	gwin->paint();
1032 }
1033 
toggle_grab_actor()1034 void Cheat::toggle_grab_actor() {
1035 	if (!enabled) return;
1036 
1037 	grab_actor = !grab_actor;
1038 	if (grab_actor)
1039 		eman->center_text("NPC Tool Actor Grabbing Enabled");
1040 	else
1041 		eman->center_text("NPC Tool Actor Grabbing Disabled");
1042 }
1043 
set_grabbed_actor(Actor * actor) const1044 void Cheat::set_grabbed_actor(Actor *actor) const {
1045 	if (!enabled || !cscreen) return;
1046 
1047 	cscreen->SetGrabbedActor(actor);
1048 }
1049 
clear_this_grabbed_actor(Actor * actor) const1050 void Cheat::clear_this_grabbed_actor(Actor *actor) const {
1051 	if (!enabled || !cscreen) return;
1052 
1053 	cscreen->ClearThisGrabbedActor(actor);
1054 }
1055 
toggle_number_npcs()1056 void Cheat::toggle_number_npcs() {
1057 	if (!enabled) return;
1058 
1059 	npc_numbers = !npc_numbers;
1060 	if (npc_numbers)
1061 		eman->center_text("NPC Numbers Enabled");
1062 	else
1063 		eman->center_text("NPC Numbers Disabled");
1064 }
1065