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