1 // nazghul - an old-school RPG engine
2 // Copyright (C) 2002, 2003 Gordon McNutt
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License as published by the Free
6 // Software Foundation; either version 2 of the License, or (at your option)
7 // any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
16 // Suite 330, Boston, MA 02111-1307 USA
17 //
18 // Gordon McNutt
19 // gmcnutt@users.sourceforge.net
20 //
21 
22 #include "cmd.h"
23 #include "../config.h" /* for USE_SKILLS */
24 #include "conv.h"
25 #include "place.h"
26 #include "constants.h"
27 #include "file.h"
28 #include "foogod.h"
29 #include "images.h"
30 #include "sprite.h"
31 #include "los.h"
32 #include "astar.h"
33 #include "common.h"
34 #include "screen.h"
35 #include "status.h"
36 #include "player.h"
37 #include "sky.h"
38 #include "map.h"
39 #include "wq.h"
40 #include "foogod.h"
41 #include "combat.h"
42 #include "cursor.h"
43 #include "Arms.h"
44 #include "event.h"
45 #include "wind.h"
46 #include "Container.h"
47 #include "dup_constants.h"
48 #include "cmdwin.h"
49 #include "vehicle.h"
50 #include "terrain.h"
51 #include "vmask.h"
52 #include "session.h"
53 #include "sched.h"
54 #include "conv.h"
55 #include "log.h"
56 #include "factions.h"
57 #include "result.h"
58 #include "dice.h"
59 #include "menus.h"
60 #include "kern_intvar.h"
61 #include "skill.h"
62 #include "skill_set.h"
63 #include "skill_set_entry.h"
64 #include "templ.h"
65 #include "occ.h"
66 #include "nazghul.h"  // for DeveloperMode
67 #include "ztats.h"
68 #include "terrain_editor.h"
69 
70 #define DEBUG
71 #include "debug.h"
72 
73 #include <string.h>
74 #include <stdlib.h>
75 #include <assert.h>
76 #include <ctype.h>
77 #include <unistd.h>     // getpid()
78 #include <errno.h>
79 
80 #define ESCAPE_CHARACTER 110
81 
82 /* Disabling the '>' command when standing over a subplace. This is generally
83  * not useful and can be abused for some towns. */
84 #ifndef ENABLE_TOWN_ZOOM_IN
85 #define ENABLE_TOWN_ZOOM_IN 0
86 #endif
87 
88 /* SAM: Using this typedef below */
89 typedef void (*v_fncptr_iiv_t) (struct place *, int x, int y, void * v);
90 typedef int (*i_fncptr_iiv_t) (struct place *, int x, int y, void * v);
91 
92 
93 /**
94  * Struct used by the movecursor function and it's mouse-handling counterparts
95  * for commands which prompt the player to select a target from the map.
96  */
97 struct movecursor_data {
98         v_fncptr_iiv_t each_tile_func;   /* called when cursor moves         */
99         i_fncptr_iiv_t each_target_func; /* called on 'enter' or leftclick   */
100         struct list *loc_list;           /* quick target list                */
101         struct list *cur_loc;            /* current target from list         */
102         int jump;                        /* distance to jump cursor          */
103         void *data;                      /* caller data passed to callbacks  */
104         char abort : 1;                  /* command was aborted              */
105 };
106 
107 /* fwd decls */
108 
109 #ifdef USE_SKILLS
110 static class Character *cmd_front_end(class Character *pc, const char *cmdstr);
111 #endif
112 static int cmd_eval_and_log_result(int result);
113 static int select_target_rlcb(struct place *place,
114                               int ox, int oy, int *x, int *y,
115                               int range,
116                               struct list *suggest,
117                               v_fncptr_iiv_t each_tile_func,
118                               i_fncptr_iiv_t each_target_func);
119 
120 /* functions */
121 
cmdAnyPartyMemberEngagedInTask(void)122 static class Character * cmdAnyPartyMemberEngagedInTask(void)
123 {
124     int num_pcs = player_party->getSize();
125     for (int i = 0; i < num_pcs; i++) {
126         class Character *pc = player_party->getMemberAtIndex(i);
127         if (pc->engagedInTask()) {
128             return pc;
129         }
130     }
131     return NULL;
132 }
133 
dirkey(struct KeyHandler * kh,int key,int keymod)134 int dirkey(struct KeyHandler *kh, int key, int keymod)
135 {
136 	int *dir = (int *) kh->data;
137 
138 	if (key >= KEY_SOUTHWEST && key <= KEY_NORTHEAST) {
139 		*dir = keyToDirection(key);
140 		return 1;
141 	}
142 
143 	if (key == SDLK_ESCAPE) {
144 		*dir = key;
145 		return 1;
146 	}
147 
148         /* Special case: let '.' mean KEY_HERE for the numeric keypad
149          * challenged. */
150         if (key == '.') {
151                 *dir = keyToDirection(KEY_HERE);
152                 return 1;
153         }
154 
155 	return 0;
156 }
157 
cardinaldirkey(struct KeyHandler * kh,int key,int keymod)158 int cardinaldirkey(struct KeyHandler *kh, int key, int keymod)
159 {
160 	int *dir = (int *) kh->data;
161 
162 	switch (key) {
163 	case KEY_NORTH:
164 	case KEY_SOUTH:
165 	case KEY_EAST:
166 	case KEY_WEST:
167 		*dir = keyToDirection(key);
168 		return 1;
169 	}
170 
171 	if (key == SDLK_ESCAPE) {
172 		*dir = key;
173 		return 1;
174 	}
175 
176 	return 0;
177 }
178 
yesnokey(struct KeyHandler * kh,int key,int keymod)179 int yesnokey(struct KeyHandler * kh, int key, int keymod)
180 {
181 	int *yesno = (int *) kh->data;
182 
183 	switch (key) {
184 	case 'y':
185 	case 'Y':
186 		*yesno = 'y';
187 		return 1;
188 	case 'n':
189 	case 'N':
190 	case CANCEL:
191 		*yesno = 'n';
192 		return 1;
193 	default:
194 		return 0;
195 	}
196 }
197 
198 enum get_number_state {
199 	GN_ALL,
200 	GN_ZERO,
201 	GN_SOME,
202 	GN_CANCEL
203 };
204 
205 /* Max number getnum() will accept */
206 const int MAX_GETNUM = 999999999;
207 
208 struct get_number_info {
209 	int digit;
210 	int state;
211 	char *prompt;
212 };
213 
214 struct get_char_info {
215         const char *string;
216         char c;
217 	int state;
218 	char *prompt;
219 };
220 
221 struct get_spell_name_data {
222 	char spell_name[MAX_WORDS_IN_SPELL_NAME + 1];
223         const char *prompt;
224 	char *ptr;
225 	int n;
226         int state;
227 };
228 
getnum(struct KeyHandler * kh,int key,int keymod)229 int getnum(struct KeyHandler *kh, int key, int keymod)
230 {
231 	struct get_number_info *info;
232 
233 	info = (struct get_number_info *) kh->data;
234 
235 	switch (info->state) {
236 	case GN_ALL:
237 		if (key == CANCEL) {
238                         cmdwin_pop();
239 			info->digit = 0;
240 			info->state = GN_CANCEL;
241 			return 1;
242 		}
243 		if (key == '\n') {
244                         cmdwin_pop();
245 			return 1;
246 		}
247 		if (key == '0') {
248                         cmdwin_pop();
249 			cmdwin_push("0");
250 			info->digit = 0;
251 			info->state = GN_ZERO;
252 			return 0;
253 		}
254 		if (isdigit(key)) {
255                         cmdwin_pop();
256 			info->digit = info->digit * 10 + key - '0';
257 			cmdwin_push("%c", key);
258 			info->state = GN_SOME;
259 			return 0;
260 		}
261 		break;
262 	case GN_ZERO:
263 		if (key == CANCEL) {
264 			info->digit = 0;
265 			info->state = GN_CANCEL;
266 			return 1;
267 		}
268 		if (key == '\n') {
269 			return 1;
270 		}
271 		if (key == '\b') {
272 			cmdwin_pop();
273 			if (info->prompt)
274 				cmdwin_spush(info->prompt);
275 			info->state = GN_ALL;
276 			return 0;
277 		}
278 		if (key == '0')
279 			return 0;
280 		if (isdigit(key)) {
281 			cmdwin_pop();
282 			info->digit = info->digit * 10 + key - '0';
283 			cmdwin_push("%c", key);
284 			info->state = GN_SOME;
285 			return 0;
286 		}
287 		break;
288 	case GN_SOME:
289 		if (key == CANCEL) {
290 			info->digit = 0;
291 			info->state = GN_CANCEL;
292 			return 1;
293 		}
294 		if (key == '\n') {
295 			return 1;
296 		}
297 		if (key == '\b') {
298 			info->digit = info->digit - (info->digit % 10);
299 			info->digit /= 10;
300 			cmdwin_pop();
301 			if (info->digit == 0) {
302 				info->state = GN_ALL;
303 				if (info->prompt)
304 					cmdwin_spush(info->prompt);
305 			}
306 			return 0;
307 		}
308 		if (isdigit(key)) {
309                         int keyval = key - '0';
310                         if ((MAX_GETNUM - keyval) >= info->digit) {
311                                 info->digit = info->digit * 10 + keyval;
312                                 cmdwin_push("%c", key);
313                         }
314 			return 0;
315 		}
316 		break;
317 	}
318 
319 	return 0;
320 }
321 
getdigit(struct KeyHandler * kh,int key,int keymod)322 int getdigit(struct KeyHandler * kh, int key, int keymod)
323 {
324         struct get_number_info *info;
325 
326         info = (struct get_number_info *) kh->data;
327 
328         if (key == CANCEL) {
329                 cmdwin_pop();
330                 info->digit = 0;
331                 return 1;
332         }
333 
334         if (isdigit(key)) {
335                 cmdwin_pop();
336                 info->digit = key - '0';
337                 if (info->digit != 0)
338                         cmdwin_push("%c", key);
339                 return 1;
340         }
341 
342         return 0;
343 }
344 
cmd_getchar(struct KeyHandler * kh,int key,int keymod)345 static int cmd_getchar(struct KeyHandler * kh, int key, int keymod)
346 {
347         struct get_char_info *info;
348 
349         info = (struct get_char_info *) kh->data;
350 
351         if (key == CANCEL) {
352                 cmdwin_pop();
353                 info->c = 0;
354                 return 1;
355         }
356 
357         if (strchr(info->string, key)) {
358                 cmdwin_pop();
359                 info->c = key;
360                 cmdwin_push("%c", key);
361                 return 1;
362         }
363 
364         return 0;
365 }
366 
anykey(struct KeyHandler * kh,int key,int keymod)367 int anykey(struct KeyHandler * kh, int key, int keymod)
368 {
369 	return 1;
370 }
371 
scroller(struct KeyHandler * kh,int key,int keymod)372 int scroller(struct KeyHandler * kh, int key, int keymod)
373 {
374 	struct ScrollerContext *context;
375 	context = (struct ScrollerContext *) kh->data;
376 
377 	switch (key) {
378 	case KEY_NORTH:
379 		statusScroll(ScrollUp);
380 		break;
381 	case KEY_SOUTH:
382 		statusScroll(ScrollDown);
383 		break;
384 	case KEY_EAST:
385 		statusScroll(ScrollRight);
386 		break;
387 	case KEY_WEST:
388 		statusScroll(ScrollLeft);
389 		break;
390 	case SDLK_PAGEUP:
391 		statusScroll(ScrollPageUp);
392 		break;
393 	case SDLK_PAGEDOWN:
394 		statusScroll(ScrollPageDown);
395 		break;
396 	case SDLK_RETURN:
397 	case SDLK_SPACE:
398         case KEY_HERE:
399 	case '\n':
400 		if (context != NULL) {
401 			context->selection =
402                                 statusGetSelected(context->selector);
403 		}
404 		return 1;
405 	case SDLK_ESCAPE:
406 	case 'q':
407 		if (context)
408 			context->abort = 1;
409 		return 1;
410 	case 'm':
411 		if (context && context->mixing) {
412 			context->done = 1;
413 			return 1;
414 		}
415 		break;
416 	default:
417 		break;
418 	}
419 
420 	return 0;
421 }
422 
mouse_button_cursor(struct MouseButtonHandler * mh,SDL_MouseButtonEvent * event)423 bool mouse_button_cursor(struct MouseButtonHandler *mh, SDL_MouseButtonEvent *event)
424 {
425         struct movecursor_data * data
426                 = (struct movecursor_data *) mh->data;
427         int mx = event->x;
428         int my = event->y;
429 
430         /* Off-map? */
431         if (mapScreenToPlaceCoords(&mx, &my)) {
432                 return false;
433         }
434 
435         /* Did the crosshair move? */
436         if (Session->crosshair->getX() != mx
437             || Session->crosshair->getY() != my) {
438 
439                 /* turn on range shading after the first move */
440                 Session->crosshair->shadeRange(true);
441 
442                 /* Move the crosshair */
443                 Session->crosshair->move(mx - Session->crosshair->getX(),
444                                          my - Session->crosshair->getY());
445                 mapSetDirty();
446 
447                 /* Need to run our visitor function on each tile? */
448                 if (data->each_tile_func) {
449                         data->each_tile_func(Session->crosshair->getPlace(),
450                                              Session->crosshair->getX(),
451                                              Session->crosshair->getY(),
452                                              data->data);
453                 }
454 
455         }
456 
457         /* target selected? */
458         if (event->button == SDL_BUTTON_LEFT) {
459                 if (data->each_target_func) {
460                         return data->each_target_func(Session->crosshair->getPlace(),
461                                                       Session->crosshair->getX(),
462                                                       Session->crosshair->getY(),
463                                                       data->data);
464                 }
465                 return 1; /* target selected */
466         }
467 
468 
469         return false;
470 }
471 
mouse_motion_cursor(struct MouseMotionHandler * mh,SDL_MouseMotionEvent * event)472 bool mouse_motion_cursor(struct MouseMotionHandler *mh, SDL_MouseMotionEvent *event)
473 {
474         struct movecursor_data * data
475                 = (struct movecursor_data *) mh->data;
476         int mx = event->x;
477         int my = event->y;
478 
479         /* Off-map? */
480         if (mapScreenToPlaceCoords(&mx, &my)) {
481                 return false;
482         }
483 
484         /* Did the crosshair NOT move? */
485         if (Session->crosshair->getX() == mx
486             && Session->crosshair->getY() == my) {
487                 return false;
488         }
489 
490         /* turn on range shading after the first move */
491         Session->crosshair->shadeRange(true);
492 
493         /* Move the crosshair */
494         Session->crosshair->move(mx - Session->crosshair->getX(),
495                                  my - Session->crosshair->getY());
496         mapSetDirty();
497 
498         /* Need to run our visitor function on each tile? */
499         if (data->each_tile_func) {
500                 data->each_tile_func(Session->crosshair->getPlace(),
501                                      Session->crosshair->getX(),
502                                      Session->crosshair->getY(),
503                                      data->data);
504         }
505 
506         /* Mouse dragging? */
507         if (event->state & SDL_BUTTON(1)
508             && data->each_target_func) {
509                 return data->each_target_func(Session->crosshair->getPlace(),
510                                               Session->crosshair->getX(),
511                                               Session->crosshair->getY(),
512                                               data->data);
513         }
514 
515 
516         return false;
517 }
518 
519 /**
520  * movecursor - move the crosshair around, possibly running a function on each
521  * tile entered by the crosshair or on each tile selected
522  */
movecursor(struct KeyHandler * kh,int key,int keymod)523 int movecursor(struct KeyHandler * kh, int key, int keymod)
524 {
525         int moved = 0;
526         struct movecursor_data * data
527                 = (struct movecursor_data *) kh->data;
528 
529         /* target selected? */
530         switch (key) {
531 	case SDLK_RETURN:
532 	case SDLK_SPACE:
533         case KEY_HERE:
534 	case '\n':
535                 if (data->each_target_func) {
536                         return data->each_target_func(Session->crosshair->getPlace(),
537                                                       Session->crosshair->getX(),
538                                                       Session->crosshair->getY(),
539                                                       data->data);
540                 }
541                 return 1; /* target selected */
542         default:
543                 break;
544         }
545 
546         /* crosshairs moved? */
547         if (keyIsDirection(key)) {
548                 int dir = keyToDirection(key);
549                 int dx = directionToDx(dir);
550                 int dy = directionToDy(dir);
551 
552                 /* Brain-dead but simple way to clamp the jump distance to
553                  * range: iteratively back-off until it's ok. */
554                 while (OutOfRange == Session->crosshair->move(dx * data->jump,
555                                                               dy * data->jump)
556                         && data->jump > 1) {
557                         data->jump--;
558                 }
559                 moved = 1;
560         } else if (isdigit(key)) {
561                 data->jump = key - '0';
562                 if (! data->jump)
563                         data->jump = 1; /* disallow zero */
564         } else {
565 
566                 struct list *old_loc = data->cur_loc;
567 
568                 switch (key) {
569 
570                 case SDLK_ESCAPE:
571                         /* Abort */
572                         data->abort = 1;
573                         return 1;   /* done */
574 
575                 case '+':
576                 case '=':
577                 case 'n':
578                         /* Next target */
579                         if (data->loc_list
580                             && ! list_empty(data->loc_list)) {
581                                 data->cur_loc = data->cur_loc->next;
582                                 if (data->cur_loc == data->loc_list) {
583                                         /* wrap around */
584                                         data->cur_loc =
585                                                 data->cur_loc->next;
586                                 }
587                         }
588                         break;
589 
590                 case '-':
591                 case 'p':
592                         /* Previous target */
593                         if (data->loc_list
594                             && ! list_empty(data->loc_list)) {
595                                 data->cur_loc = data->cur_loc->prev;
596                                 if (data->cur_loc == data->loc_list) {
597                                         /* wrap around */
598                                         data->cur_loc =
599                                                 data->cur_loc->prev;
600                                 }
601                         }
602                         break;
603                 default:
604                         break;
605                 }
606 
607                 /* Target changed? */
608                 if (old_loc != data->cur_loc) {
609                         struct location_list *loc =
610                                 (struct location_list*)data->cur_loc;
611                         Session->crosshair->move(loc->x -
612                                                  Session->crosshair->getX(),
613                                                  loc->y -
614                                                  Session->crosshair->getY());
615                         moved = 1;
616                 }
617         }
618 
619         /* Cursor was moved? */
620         if (moved) {
621                 data->jump = 1;
622                 mapSetDirty();
623                 if (data->each_tile_func) {
624                         data->each_tile_func(Session->crosshair->getPlace(),
625                                              Session->crosshair->getX(),
626                                              Session->crosshair->getY(),
627                                              data->data);
628                 }
629 
630                 /* turn on range shading after the first move */
631                 Session->crosshair->shadeRange(true);
632         }
633 
634         return 0;   /* not done */
635 }
636 
ui_select_item(void)637 struct inv_entry *ui_select_item(void)
638 {
639 	struct inv_entry *ie;
640 	struct KeyHandler kh;
641 	struct ScrollerContext sc;
642 
643         foogodSetHintText(SCROLLER_HINT);
644         foogodSetMode(FOOGOD_HINT);
645 
646 	sc.selector = InventoryItem;
647 	sc.selection = NULL;
648 	kh.fx = scroller;
649 	kh.data = &sc;
650 
651 	eventPushKeyHandler(&kh);
652 	cmdwin_push("<select>");
653 	eventHandle();
654 	cmdwin_pop();
655 	eventPopKeyHandler();
656 
657         foogodSetMode(FOOGOD_DEFAULT);
658 
659 	ie = (struct inv_entry *) sc.selection;
660 	if (ie == NULL) {
661 		cmdwin_push("none!");
662 		return NULL;
663 	}
664 
665 	cmdwin_spush(ie->type->getName());
666 
667 	return ie;
668 }
669 
select_party_member(void)670 class Character *select_party_member(void)
671 {
672 	enum StatusMode omode;
673 	class Character *character;
674 
675         if (1 == player_party->getSize()) {
676                 character = player_party->getMemberByOrder(0);
677                 /* fixme: move to cmd_front_end? */
678 		cmdwin_spush("%s", character->getName());
679                 return character;
680         }
681 
682         foogodSetHintText(SCROLLER_HINT);
683         foogodSetMode(FOOGOD_HINT);
684 	omode = statusGetMode();
685 	statusSetMode(SelectCharacter);
686 
687 	struct KeyHandler kh;
688 	struct ScrollerContext sc;
689 	sc.selector = Character;
690 	sc.selection = NULL;
691 	kh.fx = scroller;
692 	kh.data = &sc;
693 
694 	eventPushKeyHandler(&kh);
695 	cmdwin_push("<select>");
696 	eventHandle();
697 	cmdwin_pop();
698 	eventPopKeyHandler();
699 
700 	statusRepaint();
701 
702 	character = (class Character *) sc.selection;
703 
704 	if (character == NULL) {
705 		cmdwin_push("none!"); /* fixme: move to cmd_front_end? */
706 		/* Hack alert: this saves the caller from having to remember to
707 		 * do this. Doing it unconditionally is undesirable because it
708 		 * can cause status screen flashes if the old mode requires a
709 		 * short status window and the next mode requires a tall
710 		 * one. */
711 	} else {
712                 /* fixme: move to cmd_front_end? */
713 		cmdwin_spush("%s", character->getName());
714 	}
715 
716 	statusSetMode(omode);
717         foogodSetMode(FOOGOD_DEFAULT);
718 
719 	return character;
720 }
721 
getkey(void * data,int (* handler)(struct KeyHandler * kh,int key,int keymod))722 void getkey(void *data, int(*handler) (struct KeyHandler * kh, int key, int keymod))
723 {
724 	struct KeyHandler kh;
725 	kh.fx = handler;
726 	kh.data = data;
727 
728 	eventPushKeyHandler(&kh);
729 	eventHandle();
730 	eventPopKeyHandler();
731 }
732 
ui_get_direction(void)733 int ui_get_direction(void)
734 {
735 	int dir;
736 	cmdwin_push("<direction>");
737 	getkey(&dir, dirkey);
738 	cmdwin_pop();
739 	if (dir == CANCEL) {
740 		cmdwin_push("none!");
741 	} else {
742 		cmdwin_spush(directionToString(dir));
743 	}
744 	return dir;
745 }
746 
search_visitor(class Object * obj,void * arg)747 static void search_visitor(class Object *obj, void *arg)
748 {
749         class ObjectType *type = obj->getObjectType();
750         /* Caution: searching can destroy the object if it triggers traps; the
751          * arg should be protected by the caller. */
752         if (type && type->canSearch()) {
753                 type->search(obj, (class Object*)arg);
754         }
755 }
756 
cmdSearch(class Character * pc)757 bool cmdSearch(class Character *pc)
758 {
759 	int dir;
760         bool old_reveal;
761         int x, y, x2,  y2;
762         struct place *place = 0;
763 
764 	cmdwin_clear();
765 	cmdwin_spush("Search");
766 
767         /* FIXME: this is duplicated in cmdHandle(), these command functions
768          * all need to be cleaned up to ensure consistency. */
769         if (! pc) {
770                 if (player_party->get_num_living_members() == 1) {
771 			pc = player_party->get_first_living_member();
772 			cmdwin_spush("%s", pc->getName());
773 		} else {
774 			pc = select_party_member();
775 			if (pc == NULL) {
776 				return false;
777 			}
778 		}
779         }
780 
781         assert(pc);
782         place = pc->getPlace();
783         x = pc->getX();
784         y = pc->getY();
785 
786 	dir = ui_get_direction();
787 	if (dir == CANCEL)
788 		return false;
789 
790         x2 = x + directionToDx(dir);
791         y2 = y + directionToDy(dir);
792 
793         /* Caution: searching can destroy the pc if it triggers traps. The
794          * following is iterative, so protect the pc from destruction until it
795          * completes. */
796         obj_inc_ref(pc);
797         place_for_each_object_at(place, x2, y2, search_visitor, pc);
798         obj_dec_ref(pc);
799 
800 	log_begin("You find ");
801         old_reveal = Reveal;
802         Reveal = true;
803 	place_describe(place, x2, y2, PLACE_DESCRIBE_ALL);
804         log_end(".");
805         Reveal = old_reveal;
806         pc->decActionPoints(kern_intvar_get("AP_COST:search"));  // SAM: We may want a '-1' value here, to signify "all remaining AP"...
807 
808 	return true;
809 }
810 
cmdGetFilter(class Object * obj)811 static int cmdGetFilter(class Object *obj)
812 {
813         return (int)(obj->getObjectType() &&
814                      obj->getObjectType()->canGet());
815 }
816 
cmdGet(class Object * actor)817 bool cmdGet(class Object *actor)
818 {
819 	class Object *item;
820 	int dir;
821 	int x, y;
822 
823 	cmdwin_clear();
824 	cmdwin_spush("Get");
825 
826 	dir = ui_get_direction();
827 
828 	if (dir == CANCEL)
829 		return false;
830 
831 	x = actor->getX() + directionToDx(dir);
832 	y = actor->getY() + directionToDy(dir);
833 
834 	item = place_get_filtered_object(actor->getPlace(), x, y,
835 	cmdGetFilter);
836 	if (!item) {
837 		log_msg("Get - nothing there!");
838 		return false;
839 	}
840 
841 	log_begin_group();
842 
843 	while (NULL != (item = place_get_filtered_object(
844 				actor->getPlace(),
845 				x, y, cmdGetFilter)))
846 	{
847 		item->getObjectType()->get(item, actor);
848 		//do not allow too much AP debt if in combat
849 		if ((combat_get_state() == COMBAT_STATE_FIGHTING)
850 					&& ((2 * actor->getActionPoints()) + actor->getSpeed() < 0))
851 		{
852 			break;
853 		}
854 	}
855 
856 	log_end_group();
857 
858 	mapSetDirty();
859 	actor->runHook(OBJ_HOOK_GET_DONE, 0);
860 	actor->decActionPoints(kern_intvar_get("AP_COST:get_item"));  // SAM: Better to have a number of AP by item type...
861 
862 
863 	return true;
864 }
865 
cmd_describe_inv_entry(struct inv_entry * ie,void * unused)866 static void cmd_describe_inv_entry(struct inv_entry *ie, void *unused)
867 {
868         log_begin("...");
869         ie->type->describeType(ie->count);
870         log_end(NULL);
871 }
872 
cmdOpen(class Character * pc)873 bool cmdOpen(class Character * pc)
874 {
875 	int dir, x, y;
876 	class Object *mech;
877 	class Container *container;
878 
879 	cmdwin_clear();
880 	cmdwin_spush("Open");
881 
882 	// Get the party member who will open the container (in combat mode
883 	// this is passed in as a parameter).
884 	if (pc) {
885 		cmdwin_spush(pc->getName());
886 	} else {
887 		pc = select_party_member();
888 		if (pc == NULL) {
889 			return false;
890 		}
891 	}
892 
893 	dir = ui_get_direction();
894 	if (dir == CANCEL)
895 		return false;
896 
897 	if (pc) {
898 		x = place_wrap_x(pc->getPlace(),
899                                  pc->getX() + directionToDx(dir));
900 		y = place_wrap_y(pc->getPlace(),
901                                  pc->getY() + directionToDy(dir));
902 	} else {
903 		x = place_wrap_x(player_party->getPlace(),
904                                  player_party->getX() + directionToDx(dir));
905 		y = place_wrap_y(player_party->getPlace(),
906                                  player_party->getY() + directionToDy(dir));
907 	}
908 
909         /* Check for a mechanism */
910          mech = place_get_object(pc->getPlace(), x, y, mech_layer);
911 
912          /* Check for a container */
913          container = (class Container *) place_get_object(Place, x, y,
914                                                           container_layer);
915 
916          /* ignore invisible objects unless Reveal is in effect */
917          if (! Reveal) {
918                  if (mech && ! mech->isVisible())
919                          mech = 0;
920                  if (container && ! container->isVisible())
921                          container = 0;
922          }
923 
924          /* If both are present the user must select one */
925          if (mech && mech->getObjectType()->canOpen() && container) {
926 
927                  enum StatusMode omode;
928                  struct stat_list_entry statlist[2];
929                  struct KeyHandler kh;
930                  struct ScrollerContext data;
931 
932                  cmdwin_push("<select>");
933 
934                  statlist[0].sprite = mech->getSprite();
935                  snprintf(statlist[0].line1, sizeof(statlist[0].line1), "%s",
936                           mech->getName());
937                  statlist[0].line2[0] = 0;
938                  statlist[0].data = mech;
939 
940                  statlist[1].sprite = container->getSprite();
941                  snprintf(statlist[1].line1, sizeof(statlist[1].line2), "%s",
942                           container->getName());
943                  statlist[1].line2[0] = 0;
944                  statlist[1].data   = container;
945 
946                  foogodSetHintText(SCROLLER_HINT);
947                  foogodSetMode(FOOGOD_HINT);
948                  omode = statusGetMode();
949                  statusSetGenericList("Choose Target", 2, statlist);
950                  statusSetMode(GenericList);
951 
952                  data.selection = NULL;
953                  data.selector  = Generic;
954                  kh.fx   = scroller;
955                  kh.data = &data;
956                  eventPushKeyHandler(&kh);
957                  eventHandle();
958                  eventPopKeyHandler();
959 
960                  statusSetMode(omode);
961                  foogodSetMode(FOOGOD_DEFAULT);
962 
963                  /* Disqualify the object NOT selected */
964                  if (data.selection == mech)
965                          container = NULL;
966                  else
967                          mech = NULL;
968 
969                  cmdwin_pop();
970          }
971 
972          /* Open a mechanism */
973          if (mech && mech->getObjectType()->canOpen()) {
974                  cmdwin_push("%s!", mech->getName());
975                  mech->getObjectType()->open(mech, pc);
976                  mapSetDirty();
977                  pc->runHook(OBJ_HOOK_OPEN_DONE, "p", mech);
978                  pc->decActionPoints(kern_intvar_get("AP_COST:open_mechanism"));
979                  return true;
980          }
981 
982          /* Nothing to open */
983          if (NULL == container) {
984                  cmdwin_push("abort!");
985                  log_msg("Open - nothing there!");
986                  return false;
987          }
988 
989 	/* Open Container */
990 
991         log_begin_group();
992 
993         pc->runHook(OBJ_HOOK_OPEN_DONE, "p", container);
994         pc->decActionPoints(kern_intvar_get("AP_COST:open_container"));
995         cmdwin_push("%s!", container->getName());
996 
997         // Describe the contents of the container.
998         log_msg("You find:");
999         container->forEach(cmd_describe_inv_entry, NULL);
1000 
1001 	// Open the container (automagically spills all the contents onto the
1002 	// map).
1003 	container->open();
1004 
1005         // --------------------------------------------------------------------
1006         // Delete container automatically on the combat map because if
1007         // containers are stacked (and they frequently are) then the top one
1008         // always gets selected and the player can't get at the ones
1009         // underneath. On the other hand, in towns I don't want to delete
1010         // people's furniture.
1011         //
1012         // Addendum: everything stated above still holds, but now corpse loot
1013         // can also get dropped on town maps outside, so I can no longer decide
1014         // whether or not to delete a container based on context. Furthermore,
1015         // I want all containers to behave the same way as much as
1016         // possible. This is an open issue and it may take some time and user
1017         // feedback to decide what best to do, so for now I'll simply always
1018         // remove containers after opening them.
1019         // --------------------------------------------------------------------
1020 
1021         container->remove();
1022 
1023         log_end_group();
1024 
1025 	mapSetDirty();
1026 	return true;
1027 }
1028 
cmd_nop_qh(struct QuitHandler * kh)1029 static bool cmd_nop_qh(struct QuitHandler *kh)
1030 {
1031         return false;
1032 }
1033 
1034 
cmdQuit(void)1035 bool cmdQuit(void)
1036 {
1037 	int yesno;
1038  	struct QuitHandler qh;
1039 
1040         /* Bugfix: if the player tries to close the window while we're in one
1041          * of our getkey() calls, we'll enter this function recursively,
1042          * messing up the prompts. So push a nop quit handler to prevent
1043          * that. Kind of a hack: why should this function "know" it is called
1044          * by the default quit handler? */
1045 	qh.fx = cmd_nop_qh;
1046 	eventPushQuitHandler(&qh);
1047 
1048 	cmdwin_clear();
1049 	cmdwin_spush("Quit");
1050         cmdwin_spush("<y/n>");
1051 	getkey(&yesno, yesnokey);
1052 	cmdwin_pop();
1053 
1054         /* Cancel quit? */
1055 	if (yesno == 'n') {
1056                 cmdwin_spush("abort!");
1057                 Quit = false;
1058                 goto pop_qh;
1059         }
1060 
1061         cmdwin_spush("save");
1062         cmdwin_spush("<y/n>");
1063         getkey(&yesno, yesnokey);
1064         cmdwin_pop();
1065 
1066         /* Don't save? */
1067         if (yesno == 'n') {
1068                 cmdwin_spush("not saving!");
1069                 Quit = true;
1070                 goto pop_qh;
1071         }
1072 
1073         if (cmdSave()) {
1074             cmdwin_spush("saved!");
1075             log_msg("Goodbye!\n");
1076             Quit = true;
1077         } else {
1078             Quit = false;
1079         }
1080 
1081  pop_qh:
1082         eventPopQuitHandler();
1083 
1084 	return Quit;
1085 }
1086 
cmdAttack(void)1087 void cmdAttack(void)
1088 {
1089         int dir;
1090         struct move_info info;
1091         struct combat_info cinfo;
1092 
1093         // Initialize data structures.
1094         memset(&info, 0, sizeof(info));
1095         memset(&cinfo, 0, sizeof(cinfo));
1096         cinfo.move = &info;
1097         cinfo.defend = false;
1098 
1099         // Get the direction
1100 	cmdwin_clear();
1101 	cmdwin_spush("Attack");
1102         cmdwin_spush("<direction>");
1103 	getkey(&dir, cardinaldirkey);
1104 	cmdwin_pop();
1105 	if (dir == CANCEL) {
1106 		cmdwin_spush("none!");
1107 		return;
1108 	}
1109 	cmdwin_spush("%s", directionToString(dir));
1110 
1111         // Get the npc party being attacked
1112         info.dx = directionToDx(dir);
1113         info.dy =  directionToDy(dir);;
1114         info.place = player_party->getPlace();
1115         info.x = place_wrap_x(info.place, player_party->getX() + info.dx);
1116         info.y = place_wrap_y(info.place, player_party->getY() + info.dy);
1117         info.npc_party = place_get_Party(info.place, info.x, info.y);
1118 
1119         if (info.npc_party == NULL) {
1120                 cmdwin_spush("nobody there!");
1121                 log_msg("Attack - nobody there!");
1122                 return;
1123         }
1124         info.px = player_party->getX();
1125         info.py = player_party->getY();
1126 
1127         cmdwin_spush("%s", info.npc_party->getName());
1128 
1129         // If the npc is not hostile then get player confirmation.
1130         if (! are_hostile(info.npc_party, player_party)) {
1131                 int yesno;
1132                 cmdwin_spush("attack non-hostile");
1133                 cmdwin_spush("<y/n>");
1134                 getkey(&yesno, yesnokey);
1135                 cmdwin_pop();
1136                 if (yesno == 'n') {
1137                         cmdwin_spush("no");
1138                         return;
1139                 }
1140                 cmdwin_spush("yes");
1141 
1142                 make_hostile(info.npc_party, player_party);
1143         }
1144 
1145         // Log the attack.
1146         log_begin("You attack ");
1147         info.npc_party->describe();
1148         log_end(".");
1149 
1150         // Enter combat
1151         combat_enter(&cinfo);
1152 }
1153 
cmdDeveloperEval(struct session * session)1154 void cmdDeveloperEval(struct session *session)
1155 {
1156         unsigned int len = 1024;
1157         char *buf = (char*)calloc(len, sizeof(char));
1158         if (!buf) {
1159                 log_msg("Eval: not enough memory!");
1160                 return;
1161         }
1162 
1163         cmdwin_clear();
1164         cmdwin_push("Eval:");
1165 
1166         if (!ui_getline_filtered(buf, len, NULL)) {
1167                 log_msg("Eval: abort");
1168                 cmdwin_push("abort!");
1169                 goto cleanup;
1170         }
1171 
1172         log_msg("Eval: %s", buf);
1173         session_eval(session, buf);
1174 
1175  cleanup:
1176         free(buf);
1177 }
1178 
cmdFire(void)1179 void cmdFire(void)
1180 {
1181 	int dir;
1182 
1183 	cmdwin_clear();
1184 	cmdwin_spush("Fire");
1185 
1186         class Vehicle *vehicle = player_party->getVehicle();
1187 	if ((!vehicle ||
1188              !vehicle->getOrdnance())) {
1189                 // SAM:
1190                 // In future, we may check for adjacent "cannon"
1191                 // mechanisms here (as in U5).
1192 		cmdwin_spush("No cannons available!");
1193                 log_msg("Fire - no cannons!");
1194 		return;
1195 	}
1196 
1197 	cmdwin_spush("%s", vehicle->getOrdnance()->getName());
1198         cmdwin_spush("<direction>");
1199 	getkey(&dir, dirkey);
1200 	cmdwin_pop();
1201 
1202 	if (dir == CANCEL) {
1203 		cmdwin_spush("none!");
1204 		return;
1205 	}
1206 
1207 	cmdwin_spush("%s", directionToString(dir));
1208 	if (! vehicle->fire_weapon(directionToDx(dir),
1209                                                  directionToDy(dir),
1210                                                  player_party)) {
1211 		cmdwin_spush("Not a broadside!");
1212                 log_msg("Fire - not a broadside!");
1213 		return;
1214         }
1215 }
1216 
cmdReady(class Character * member)1217 bool cmdReady(class Character * member)
1218 {
1219 	bool committed = false;
1220 	struct inv_entry *ie;
1221 	struct KeyHandler kh;
1222 	struct ScrollerContext sc;
1223 	const char *msg = 0;
1224 
1225 	cmdwin_clear();
1226 	cmdwin_spush("Ready");
1227 
1228         // Select user
1229         if (member) {
1230 		cmdwin_spush("%s", member->getName());
1231         } else {
1232                 member = select_party_member();
1233                 if (member == NULL)
1234                         return false;
1235 
1236                 if (member->isCharmed()) {
1237                         cmdwin_push("Charmed!");
1238                         log_msg("Ready - charmed!");
1239                         return false;
1240                 }
1241 
1242         }
1243 
1244         log_begin_group();
1245         log_msg("%s readies arms:", member->getName());
1246 
1247 	statusSelectCharacter(member->getOrder());
1248 
1249 	player_party->sortReadiedItems(member);
1250         foogodSetHintText(SCROLLER_HINT);
1251         foogodSetMode(FOOGOD_HINT);
1252 	statusSetMode(Ready);
1253 	sc.selector = InventoryItem;
1254 	kh.fx = scroller;
1255 	kh.data = &sc;
1256 	eventPushKeyHandler(&kh);
1257 
1258         cmdwin_spush("<select/ESC>");
1259 
1260 	for (;;) {
1261 
1262 		sc.selection = NULL;
1263 
1264 		eventHandle();
1265                 cmdwin_pop();
1266 
1267 		ie = (struct inv_entry *) sc.selection;
1268 		if (ie == NULL) {
1269 			cmdwin_spush("done!");
1270 			break;
1271 		}
1272 
1273 		committed = true;
1274 
1275 		class ArmsType *arms = (class ArmsType *) ie->type;
1276 
1277                 log_begin("%s - ", arms->getName());
1278 
1279 		if (ie->ref && member->unready(arms)) {
1280 			msg = "unreadied!";
1281 			member->decActionPoints(arms->getRequiredActionPoints());
1282 			statusRepaint();
1283 		} else {
1284 
1285 			switch (member->ready(arms)) {
1286 			case Character::Readied:
1287 				statusRepaint();
1288 				msg = "readied!";
1289 				member->decActionPoints(arms->getRequiredActionPoints());
1290            /* Move the readied item to the front of the
1291             * list for easy access next time, and to
1292             * percolate frequently-used items up to the
1293             * top. */
1294            //player_party->inventory->moveToFront(ie);
1295            /* After re-ordering the list, reset the status
1296             * viewer to synch it back up with the new
1297             * list. */
1298            //statusSetMode(Ready);
1299 				break;
1300 			case Character::NoAvailableSlot:
1301 				msg = "all full!";
1302 				break;
1303 			case Character::WrongType:
1304 				msg = "can't use!";
1305 				break;
1306 			case Character::TooHeavy:
1307 				msg = "too heavy!";
1308 				break;
1309 			default:
1310 				assert(false);
1311 				break;
1312 			}
1313 		}
1314 
1315 		cmdwin_spush("%s %s", arms->getName(), msg);
1316                 log_end(msg);
1317 
1318      	//do not allow too much AP debt if in combat
1319 		if ((combat_get_state() == COMBAT_STATE_FIGHTING)
1320 					&& ((2 * member->getActionPoints()) + member->getSpeed() < 0))
1321 		{
1322 			break;
1323 		}
1324 
1325 	}
1326 
1327 	eventPopKeyHandler();
1328 	statusSetMode(ShowParty);
1329         foogodSetMode(FOOGOD_DEFAULT);
1330 
1331         if (committed) {
1332                 player_party->sortReadiedItems(member);
1333                 member->runHook(OBJ_HOOK_READY_DONE, 0);
1334         }
1335 
1336         log_end_group();
1337 
1338 	return committed;
1339 }
1340 
cmd_init_movecursor_data(struct movecursor_data * data,struct list * suggest)1341 static void cmd_init_movecursor_data(struct movecursor_data *data,
1342                                      struct list *suggest)
1343 {
1344         struct list *entry = 0;
1345 
1346         memset(data, 0, sizeof(*data));
1347         data->jump = 1;
1348 
1349         if (! suggest)
1350                 return;
1351 
1352         /* Copy the head of the list. */
1353         data->loc_list = suggest;
1354         data->cur_loc = data->loc_list;
1355 
1356         /* Look for the crosshair in the suggest list. */
1357         list_for_each(suggest, entry) {
1358                 struct location_list *loc =
1359                         (struct location_list*)entry;
1360                 if (loc->x == Session->crosshair->getX()
1361                     && loc->y == Session->crosshair->getY()) {
1362                         data->cur_loc = &loc->list;
1363                         break;
1364                 }
1365         }
1366 }
1367 
select_target(int ox,int oy,int * x,int * y,int range,struct list * suggest)1368 int select_target(int ox, int oy, int *x, int *y, int range,
1369                   struct list *suggest)
1370 {
1371         return select_target_rlcb(Place, ox, oy, x, y, range, suggest, 0, 0);
1372 }
1373 
ui_select_target_req_init(ui_select_target_req_t * req)1374 void ui_select_target_req_init(ui_select_target_req_t *req)
1375 {
1376         memset(req, 0, sizeof(*req));
1377         list_init(&req->suggest);
1378 }
1379 
select_target_rlcb(struct place * place,int ox,int oy,int * x,int * y,int range,struct list * suggest,v_fncptr_iiv_t each_tile_func,i_fncptr_iiv_t each_target_func)1380 static int select_target_rlcb(struct place *place,
1381                               int ox, int oy,
1382                               int *x, int *y,
1383                               int range,
1384                               struct list *suggest,
1385                               v_fncptr_iiv_t each_tile_func,
1386                               i_fncptr_iiv_t each_target_func)
1387 {
1388         ui_select_target_req_t req;
1389         int ret;
1390 
1391         /* convert args to a targeting request */
1392         ui_select_target_req_init(&req);
1393         req.place = place;
1394         req.x1 = ox;
1395         req.y1 = oy;
1396         req.x2 = *x;
1397         req.y2 = *y;
1398         req.move = each_tile_func;
1399         req.select = each_target_func;
1400         req.tiles = templ_new_from_range(range);
1401         templ_set_origin(req.tiles, ox, oy);
1402         if (suggest) {
1403                 list_move(&req.suggest, suggest);
1404         }
1405 
1406         /* call generic target selection */
1407         ret = ui_select_target_generic(&req);
1408 
1409         /* convert back */
1410         *x = req.x2;
1411         *y = req.y2;
1412         if (suggest) {
1413                 list_move(suggest, &req.suggest);
1414         }
1415         templ_unref(req.tiles);
1416 
1417         return ret;
1418 }
1419 
ui_select_target_generic(ui_select_target_req_t * req)1420 int ui_select_target_generic(ui_select_target_req_t *req)
1421 {
1422         struct movecursor_data data;
1423         struct KeyHandler kh;
1424         struct MouseButtonHandler mbh;
1425         struct MouseMotionHandler mmh;
1426 
1427         Session->crosshair->setZone(req->tiles);
1428         Session->crosshair->setViewportBounded(1);
1429         Session->crosshair->setOrigin(req->x1, req->y1);
1430         Session->crosshair->relocate(req->place, req->x2, req->y2);
1431         Session->show_boxes=1;
1432         mapSetDirty();
1433 
1434         cmd_init_movecursor_data(&data, &req->suggest);
1435         data.each_tile_func   = req->move;
1436         data.each_target_func = req->select;
1437         data.data = req->data;
1438 
1439         kh.fx   = movecursor;
1440         kh.data = &data;
1441 
1442         mbh.fx = mouse_button_cursor;
1443         mbh.data = &data;
1444 
1445         mmh.fx = mouse_motion_cursor;
1446         mmh.data = &data;
1447 
1448         eventPushMouseButtonHandler(&mbh);
1449         eventPushKeyHandler(&kh);
1450         cmdwin_spush("<target> (ESC to exit)");
1451         eventHandle();
1452         cmdwin_pop();
1453         eventPopKeyHandler();
1454         eventPopMouseButtonHandler();
1455 
1456         Session->show_boxes=0;
1457         req->x2 = Session->crosshair->getX();
1458         req->y2 = Session->crosshair->getY();
1459         Session->crosshair->remove();
1460         Session->crosshair->setZone(0);
1461         mapSetDirty();
1462 
1463         if (data.abort) {
1464                 cmdwin_spush("Done.");
1465                 return -1;
1466         }
1467 
1468         return 0;
1469 }
1470 
select_target_with_doing(int ox,int oy,int * x,int * y,int range,v_fncptr_iiv_t each_tile_func,i_fncptr_iiv_t each_target_func)1471 int select_target_with_doing(int ox, int oy, int *x, int *y,
1472                              int range,
1473                              v_fncptr_iiv_t each_tile_func,
1474                              i_fncptr_iiv_t each_target_func)
1475 {
1476         return select_target_rlcb(Place, ox, oy, x, y, range, 0,
1477                                   each_tile_func,
1478                                   each_target_func);
1479 }
1480 
cmdHandle(class Character * pc)1481 bool cmdHandle(class Character * pc)
1482 {
1483 	// SAM: Adding (H)andle command...
1484 	int x;
1485 	int y;
1486 
1487 	cmdwin_clear();
1488 	cmdwin_spush("Handle");
1489 
1490 	if (pc) {
1491 		// A party member was specified as a parameter, so this must be
1492 		// combat mode. Use the party member's location as the origin.
1493 		x = pc->getX();
1494 		y = pc->getY();
1495 		cmdwin_spush("%s", pc->getName());
1496 	} else {
1497 		// Must be party mode. Use the player party's location as the
1498 		// origin.
1499 		x = player_party->getX();
1500 		y = player_party->getY();
1501 
1502 		// And find out what the party member is Handling (so we can
1503 		// print the name). If only one party member then select the
1504 		// only one.
1505 		if (player_party->get_num_living_members() == 1) {
1506 			pc = player_party->get_first_living_member();
1507 			cmdwin_spush("%s", pc->getName());
1508 		} else {
1509 			pc = select_party_member();
1510 			if (pc == NULL) {
1511 				return false;
1512 			}
1513 		}
1514 	}
1515 
1516 	// *** Pick a target ***
1517 
1518 	if (select_target(x, y, &x, &y, 1, 0) == -1)
1519 		return false;
1520 
1521         // Try to find a mech
1522 	class Object *mech;
1523 	mech = place_get_object(Place, x, y, mech_layer);
1524 	if (! mech
1525             || ! mech->getObjectType()->canHandle()
1526             || (! mech->isVisible()
1527                 && ! Reveal)) {
1528                 cmdwin_spush("nothing!");
1529                 log_msg("Handle - nothing there to handle!");
1530                 return false;
1531         }
1532 
1533         // Handle it (sometimes mechs are intentionally nameless so that they
1534         // remain hidden from x)amine and s)earch commands)
1535         const char *mechName=mech->getName();
1536         if (!mechName) {
1537                 mechName = "a hidden mechanism";
1538         }
1539         cmdwin_spush("%s", mechName);
1540         log_msg("%s handles %s", pc->getName(), mechName);
1541         mech->getObjectType()->handle(mech, pc);
1542         pc->runHook(OBJ_HOOK_HANDLE_DONE, "p", mech);
1543         pc->decActionPoints(kern_intvar_get("AP_COST:handle_mechanism"));
1544         mapSetDirty();
1545 
1546         // I think the following was added to update LOS in cases where the
1547         // mech changed state and changed LOS. Not sure what happens in
1548         // character mode.
1549         //player_party->updateView();
1550 
1551 	return true;
1552 }
1553 
cmdUse(class Character * member,int flags)1554 bool cmdUse(class Character * member, int flags)
1555 {
1556 	struct inv_entry *ie;
1557 	class ObjectType *item;
1558         int result;
1559 
1560 	cmdwin_clear();
1561 	cmdwin_spush("Use");
1562 
1563         // Select user
1564         if (flags & CMD_SELECT_MEMBER) {
1565                 member = select_party_member();
1566                 if (member == NULL)
1567                         return false;
1568         } else {
1569                 assert(member);
1570                 cmdwin_spush("%s", member->getName());
1571         }
1572 	statusSelectCharacter(member->getOrder());
1573 
1574         // select item to use
1575 	statusSetMode(Use);
1576 	ie = ui_select_item();
1577 	statusSetMode(ShowParty);
1578 	if (ie == NULL) {
1579 		return false;
1580         }
1581 
1582         /* warning: assume usable item came from player inventory; move it to
1583          * the front of the list so that frequently-used items percolate to the
1584          * top for easy selection by the player. Oh, and do this *before* using
1585          * it, since using it may delete the ie if it is a consumable item and
1586          * the last one in inventory. */
1587         player_party->inventory->moveToFront(ie);
1588 
1589 	item = ie->type;
1590         assert(item->isUsable());
1591 
1592         // Use the item
1593         log_begin("%s: %s - ", member->getName(), item->getName());
1594 	result = item->use(member);
1595         cmd_eval_and_log_result(result);
1596         log_end(0);
1597 
1598         member->runHook(OBJ_HOOK_USE_DONE, "p", item);
1599 
1600         // Item's appear to decrement AP in the script...
1601         //member->decActionPoints(kern_intvar_get("AP_COST:use_item"));
1602 	statusRepaint();
1603 
1604 	return true;
1605 }
1606 
1607 /* Helper function called by cmdNewOrder: */
cmd_switch_party_leader(class Character * old_leader,class Character * new_leader)1608 static void cmd_switch_party_leader(class Character *old_leader,
1609                                     class Character *new_leader)
1610 {
1611         new_leader->setLeader(true);
1612         old_leader->setLeader(false);
1613         old_leader->endTurn();
1614         old_leader->setControlMode(CONTROL_MODE_FOLLOW);
1615         player_party->setLeader(new_leader);
1616 }
1617 
cmdNewOrder(void)1618 void cmdNewOrder(void)
1619 {
1620 	class Character *pc1, *pc2;
1621 
1622         switch (player_party->getSize()) {
1623         case 0:
1624                 assert(0);
1625                 break;
1626         case 1:
1627                 log_msg("New Order - only one party member!");
1628                 return;
1629         case 2:
1630                 pc1 = player_party->getMemberByOrder(0);
1631                 pc2 = player_party->getMemberByOrder(1);
1632                 goto swap;
1633         }
1634 
1635 	cmdwin_clear();
1636 	cmdwin_spush("Switch");
1637 
1638         // Set the mode now - before calling select_party_member - so that the
1639         // screen will not flash back to a short status window between the two
1640         // calls to select_party_member.
1641         statusSetMode(SelectCharacter);
1642 
1643 	pc1 = select_party_member();
1644 	if (pc1 == NULL) {
1645                 statusSetMode(ShowParty);
1646 		return;
1647         }
1648 
1649 	cmdwin_spush("with");
1650 
1651 	pc2 = select_party_member();
1652 	if (pc2 == NULL) {
1653                 statusSetMode(ShowParty);
1654 		return;
1655 	}
1656 
1657         statusSetMode(ShowParty);
1658  swap:
1659         player_party->switchOrder(pc1, pc2);
1660 
1661 	log_msg("New Order: %s switched with %s\n", pc1->getName(),
1662 		     pc2->getName());
1663 
1664         // If one of the switched members was the party leader then make the
1665         // other one the new leader (unless the other one is dead or otherwise
1666         // incapable of being a party leader).
1667         if (pc1->isLeader() && pc2->canBeLeader()) {
1668                 cmd_switch_party_leader(pc1, pc2);
1669         } else if (pc2->isLeader() && pc1->canBeLeader()) {
1670                 cmd_switch_party_leader(pc2, pc1);
1671         }
1672 
1673 	statusRepaint();
1674 }
1675 
run_combat(bool camping,class Character * guard,int hours,class Object * foe)1676 static void run_combat(bool camping, class Character * guard, int hours,
1677                        class Object *foe)
1678 {
1679 	struct move_info minfo;
1680 	struct combat_info cinfo;
1681 
1682         assert(!foe || foe->isType(PARTY_ID));
1683 
1684 	memset(&minfo, 0, sizeof(minfo));
1685 	minfo.place = Place;
1686 	minfo.x = player_party->getX();
1687 	minfo.y = player_party->getY();
1688         minfo.px = minfo.x;
1689         minfo.py = minfo.y;
1690         minfo.npc_party = (class Party*)foe;
1691 
1692 	memset(&cinfo, 0, sizeof(cinfo));
1693 	cinfo.camping = camping;
1694 	cinfo.guard = guard;
1695 	cinfo.hours = hours;
1696 	cinfo.move = &minfo;
1697 
1698         // Is there an enemy?
1699         if (foe) {
1700                 // Yes, so I assume the player party is being attacked
1701                 // (currently this happens as a result of conversation). To
1702                 // setup the proper orientation of the parties I need to get
1703                 // the direction vector. The direction should be from the foe
1704                 // to the player.
1705                 cinfo.defend = true;
1706                 place_get_direction_vector(minfo.place,
1707                                            foe->getX(), foe->getY(),
1708                                            minfo.x, minfo.y,
1709                                            &minfo.dx, &minfo.dy);
1710         } else {
1711                 // No, so we're camping or zooming in. Party values are fine
1712                 // here.
1713                 minfo.dx = player_party->getDx();
1714                 minfo.dy = player_party->getDy();
1715         }
1716 
1717 	combat_enter(&cinfo);
1718 }
1719 
1720 struct talk_info {
1721         class Object *origin;
1722         class Object *nearest;
1723         int min_distance;
1724         int range;
1725         struct list suggest;
1726 };
1727 
1728 /* cmd_talk_info_visitor - called on each object in the current place. Used by
1729  * cmdTalk() to find the nearest conversant and build a list of other available
1730  * conversants. */
cmd_talk_info_visitor(class Object * obj,void * data)1731 static void cmd_talk_info_visitor(class Object *obj, void *data)
1732 {
1733         struct location_list *entry = 0;
1734         int dist = 0;
1735         struct talk_info *info =
1736                 (struct talk_info*)data;
1737 
1738         if (! obj->getConversation())
1739                 return;
1740 
1741         if (obj->isPlayerControlled())
1742                 return;
1743 
1744         if (TimeStop)
1745                 return;
1746 
1747         if (! obj->isVisible() && ! Reveal)
1748                 return;
1749 
1750         /* Filter out objects not in los of the subject */
1751         if (! place_in_los(info->origin->getPlace(),
1752                            info->origin->getX(),
1753                            info->origin->getY(),
1754                            obj->getPlace(),
1755                            obj->getX(),
1756                            obj->getY()))
1757                 return;
1758 
1759 
1760         /* Filter out objects not in range of conversation. */
1761         dist = place_flying_distance(info->origin->getPlace(),
1762                                      info->origin->getX(),
1763                                      info->origin->getY(),
1764                                      obj->getX(),
1765                                      obj->getY());
1766 
1767         if (dist > info->range)
1768                 return;
1769 
1770         /* Add this conversant to the suggestion list. */
1771         entry = (struct location_list*)malloc(sizeof(*entry));
1772         assert(entry);
1773         entry->x = obj->getX();
1774         entry->y = obj->getY();
1775         list_add_tail(&info->suggest, &entry->list);
1776 
1777         /* Remember the nearest conversant. */
1778         if (! info->nearest
1779             || dist < info->min_distance) {
1780                 info->nearest = obj;
1781                 info->min_distance = dist;
1782         }
1783 }
1784 
1785 /* cmd_get_talk_info - find the nearest conversant and build the
1786  * 'suggest' list. */
cmd_get_talk_info(struct talk_info * info,class Object * member,int range)1787 static class Object *cmd_get_talk_info(struct talk_info *info,
1788                                              class Object *member,
1789                                              int range)
1790 {
1791         memset(info, 0, sizeof(info));
1792         info->origin = member;
1793         info->nearest = NULL;
1794         info->min_distance = 0;
1795         info->range = range;
1796         list_init(&info->suggest);
1797 
1798         place_for_each_object(member->getPlace(),
1799                               cmd_talk_info_visitor,
1800                               info);
1801 
1802         return info->nearest;
1803 }
1804 
1805 /* cmd_cleanup_talk_info - free the 'suggest' list. */
cmd_cleanup_talk_info(struct talk_info * info)1806 static void cmd_cleanup_talk_info(struct talk_info *info)
1807 {
1808         struct list *entry;
1809         for (entry = info->suggest.next; entry != &info->suggest; ) {
1810                 struct location_list *loc = (struct location_list*)entry;
1811                 entry = entry->next;
1812                 list_remove(&loc->list);
1813                 free(loc);
1814         }
1815 }
1816 
cmdTalk(Object * member)1817 void cmdTalk(Object *member)
1818 {
1819 	struct conv *conv = NULL;
1820         class Object *obj, *conversant = NULL;
1821         int x, y;
1822         struct talk_info info;
1823         const int max_distance = 5;
1824 
1825 	// *** Prompt user & check if valid ***
1826 
1827 	cmdwin_clear();
1828 	cmdwin_spush("Talk");
1829 
1830         if (! member) {
1831                 member = select_party_member();
1832                 if (! member)
1833                         return;
1834         }
1835 
1836         // start cursor on nearest object with a conversation
1837         conversant = cmd_get_talk_info(&info, member, max_distance);
1838         if (! conversant) {
1839                 conversant = member;
1840         }
1841 
1842         x = conversant->getX();
1843         y = conversant->getY();
1844 
1845 	if (select_target(member->getX(), member->getY(),
1846                           &x, &y, max_distance, &info.suggest) == -1) {
1847                 goto cleanup;
1848 	}
1849 
1850 	obj = place_get_object(Place, x, y, being_layer);
1851 
1852 	if (!obj) {
1853                 cmdwin_spush("nobody there!");
1854                 log_msg("Try talking to a PERSON.");
1855                 goto cleanup;
1856         }
1857 
1858         // This next bit was added to support talking to parties, where the
1859         // speaker is not the party itself.
1860         obj = obj->getSpeaker();
1861         if (! obj) {
1862                 cmdwin_spush("cancel");
1863                 goto cleanup;
1864         }
1865 
1866         if (TimeStop && !obj->isPlayerPartyMember()) {
1867                 cmdwin_spush("time stopped!");
1868                 log_msg("This person seems frozen in time.");
1869                 goto cleanup;
1870         }
1871 
1872         conv = obj->getConversation();
1873         if (!conv) {
1874 		cmdwin_spush("no response!");
1875                 log_begin("No response from ");
1876                 obj->describe();
1877                 log_end(".");
1878                 goto cleanup;
1879         }
1880 
1881 	cmdwin_spush(obj->getName());
1882 
1883 	if (((obj->getLayer() == being_layer)
1884              && ((class Character*)obj)->isAsleep())) {
1885 		log_msg("Zzzz...\n");
1886                 goto cleanup;
1887 	}
1888 
1889         conv_enter(obj, member, conv);
1890 	mapSetDirty();
1891 
1892  cleanup:
1893 
1894         cmd_cleanup_talk_info(&info);
1895 
1896         return;
1897 }
1898 
cmdZtats(class Character * pc)1899 bool cmdZtats(class Character * pc)
1900 {
1901 	statusRunApplet(ztats_get_applet()); /* runs until user ESC */
1902 	statusSetMode(ShowParty); /* restore default status mode */
1903 	return false;
1904 }
1905 
select_hours(int allow_sunrise)1906 static int select_hours(int allow_sunrise)
1907 {
1908 	struct get_char_info info;
1909 
1910         if (allow_sunrise) {
1911                 cmdwin_spush("<hours[0-9]/[s]unrise>");
1912                 info.string = "0123456789sS";
1913         } else {
1914                 cmdwin_spush("<hours[0-9]>");
1915                 info.string = "0123456789";
1916         }
1917 
1918 	info.c = '0';
1919 
1920 	getkey(&info, &cmd_getchar);
1921 
1922 	if (! info.c || info.c == '0') {
1923                 cmdwin_pop();
1924 		cmdwin_spush("none!");
1925                 return 0;
1926         }
1927         else if (allow_sunrise
1928                  && (info.c == 's' ||
1929                      info.c == 'S')) {
1930                 int hour;
1931                 int sunrise;
1932 
1933                 cmdwin_pop();
1934                 cmdwin_push("until sunrise");
1935                 hour = clock_time_of_day() / 60;
1936                 sunrise = SUNRISE_HOUR + 1;
1937                 if (hour < sunrise)
1938                         return sunrise - hour;
1939                 return HOURS_PER_DAY - hour + sunrise;
1940         }
1941 	else if (info.c == '1') {
1942 		cmdwin_push(" hour");
1943                 return 1;
1944         }
1945 	else {
1946 		cmdwin_push(" hours");
1947                 return info.c - '0';
1948         }
1949 }
1950 
ui_get_quantity(int max)1951 int ui_get_quantity(int max)
1952 {
1953 	struct get_number_info info;
1954         char prompt[64];
1955 
1956         /* Push the prompt but remember it for use within getnum() */
1957 	if (max == -1) {
1958                 snprintf(prompt, sizeof(prompt), "<quantity>");
1959 	} else {
1960                 snprintf(prompt, sizeof(prompt),
1961                          "<quantity[0-%d]/RET=%d>", max, max);
1962 	}
1963 
1964 	info.digit = 0;
1965 	info.state = GN_ALL;
1966 	info.prompt = prompt;
1967 
1968         cmdwin_spush(info.prompt);
1969 	getkey(&info, getnum);
1970 
1971 	if (info.state == GN_ALL) {
1972 		if (max == -1)
1973 			info.digit = 0;
1974 		else
1975 			info.digit = max;
1976 	} else if (info.state == GN_CANCEL)
1977 		cmdwin_spush("none!");
1978 
1979 	return info.digit;
1980 }
1981 
cmd_camp_in_wilderness(class Party * camper)1982 int cmd_camp_in_wilderness(class Party *camper)
1983 {
1984 	int hours, yesno;
1985 	class Character *guard = 0;
1986 
1987 	cmdwin_clear();
1988 	cmdwin_spush("Camp");
1989 
1990 	if (!place_is_passable(camper->getPlace(), camper->getX(),
1991                                camper->getY(), camper, PFLAG_IGNOREVEHICLES)) {
1992 		cmdwin_spush("not here!");
1993                 log_msg("Camp - not here!");
1994 		return 0;
1995 	}
1996 
1997         if (place_get_subplace(camper->getPlace(),
1998                                camper->getX(),
1999                                camper->getY())) {
2000 		cmdwin_spush("not here!");
2001                 log_msg("Camp - not here!");
2002                 return 0;
2003         }
2004 
2005 	hours = select_hours(1);
2006 	if (hours == 0)
2007 		return 0;
2008 
2009         cmdwin_spush(""); /* for the '-' */
2010 	cmdwin_spush("set a watch");
2011         cmdwin_spush("<y/n>");
2012 	getkey(&yesno, &yesnokey);
2013 
2014 	if (yesno == 'y') {
2015 
2016 		cmdwin_pop();
2017 		guard = select_party_member();
2018 		if (!guard) {
2019                         cmdwin_pop();
2020 			cmdwin_push("no watch");
2021 		}
2022                 else if (guard->isDead()) {
2023                         log_msg("You prop up the corpse and wave off "
2024                                 "the flies...");
2025                 }
2026                 // else select_party_member() prints the name
2027 
2028 	} else {
2029 		cmdwin_pop();
2030 		cmdwin_spush("no watch");
2031 	}
2032 
2033 	player_party->beginCamping(guard, hours);
2034         camper->endTurn();
2035 	run_combat(true, guard, hours, NULL);
2036 
2037         return 0;
2038 }
2039 
cmdLoiter(class Being * subject)2040 void cmdLoiter(class Being *subject)
2041 {
2042     int hours = 0;
2043 
2044         cmdwin_clear();
2045         cmdwin_spush("Loiter");
2046 
2047         /* Check if enemies are around. */
2048         if (place_contains_hostiles(subject->getPlace(), subject)) {
2049                 cmdwin_spush("foes nearby!");
2050                 log_msg("Loiter - foes nearby!");
2051                 return;
2052         }
2053 
2054         /* Check if any party members are engaged in a task. */
2055         class Character *pc;
2056         if ((pc = cmdAnyPartyMemberEngagedInTask())) {
2057             log_msg("Loiter - %s engaged in task!", pc->getName());
2058             cmdwin_spush("busy with tasks!");
2059             return;
2060         }
2061 
2062         /* Prompt for the number of hours. */
2063         hours = select_hours(0);
2064         if (!hours) {
2065                 return;
2066         }
2067 
2068         /* Tell the party to start loitering. */
2069         cmdwin_spush("loitering...");
2070         player_party->beginLoitering(hours);
2071 
2072         /* End the turn. */
2073         subject->endTurn();
2074 
2075 }
2076 
cmd_camp_in_town(class Character * camper)2077 int cmd_camp_in_town(class Character *camper)
2078 {
2079         int hours;
2080 
2081         cmdwin_clear();
2082         cmdwin_spush("Rest");
2083 
2084         // Party must be in follow mode.
2085         if (player_party->getPartyControlMode() != PARTY_CONTROL_FOLLOW) {
2086                 cmdwin_spush("must be in follow mode!");
2087                 log_begin_group();
2088                 log_msg("Camp - party not in follow mode!");
2089                 log_msg("(Hint: hit 'f' to enter follow mode)");
2090                 log_end_group();
2091                 return 0;
2092         }
2093 
2094         // Check for an object that will serve as a bed.
2095         if (place_get_object(camper->getPlace(), camper->getX(),
2096                              camper->getY(),  bed_layer) == NULL) {
2097                 cmdwin_spush("no bed!");
2098                 log_msg("Camp - no bed here!");
2099                 return 0;
2100         }
2101 
2102         // Rendezvous the party around the bed.
2103         if (! player_party->rendezvous(camper->getPlace(), camper->getX(),
2104                                        camper->getY())) {
2105                 log_msg("Camp - party can't rendezvous!");
2106                 return 0;
2107         }
2108 
2109         // Prompt for the number of hours to sleep.
2110         hours = select_hours(! camper->getPlace()->underground);
2111         if (hours == 0)
2112                 return 0;
2113 
2114         // Put the party in "sleep" mode before returning back to the main
2115         // event loop.
2116         cmdwin_spush("resting...");
2117         player_party->beginResting(hours);
2118         camper->endTurn();
2119 
2120         return TURNS_PER_HOUR;
2121 }
2122 
get_spell_name(struct KeyHandler * kh,int key,int keymod)2123 int get_spell_name(struct KeyHandler *kh, int key, int keymod)
2124 {
2125 	struct get_spell_name_data *ctx;
2126 	char *word, letter;
2127 
2128 	ctx = (struct get_spell_name_data *) kh->data;
2129 
2130         switch (ctx->state) {
2131 
2132         case GN_ZERO: /* No spell words are entered yet and the prompt is
2133                        * sitting there. */
2134 
2135                 /* Done? */
2136                 if (key == '\n') {
2137                         cmdwin_pop();
2138                         return 1;
2139                 }
2140 
2141                 /* Abort? */
2142                 if (key == CANCEL) {
2143                         cmdwin_pop();
2144                         ctx->spell_name[0] = 0;
2145                         return 1;
2146                 }
2147 
2148                 /* A letter? */
2149                 if (isalpha(key)) {
2150 
2151                         /* Lookup the word that goes with the letter. */
2152                         letter = toupper(key);
2153                         word = magic_lookup_word(&Session->magic, letter);
2154 
2155                         /* Valid word? */
2156                         if (word) {
2157 
2158                                 /* Clear prompt, show the word and advance. */
2159                                 cmdwin_pop();
2160                                 cmdwin_push(word);
2161                                 *ctx->ptr = letter;
2162                                 ctx->ptr++;
2163                                 ctx->n++;
2164                                 ctx->state = GN_SOME;
2165                         }
2166                 }
2167                 return 0;
2168 
2169         case GN_SOME: /* One or more words are already entered. */
2170 
2171                 /* Done? Ensure null-termination. */
2172                 if (key == '\n') {
2173                         /* Segment-push a null string to force a '-' following
2174                          * the spell name. */
2175                         cmdwin_spush(0);
2176                         *ctx->ptr = 0;
2177                         return 1;
2178                 }
2179 
2180                 /* Abort? Terminate string at beginning. */
2181                 if (key == CANCEL) {
2182                         ctx->spell_name[0] = 0;
2183                         return 1;
2184                 }
2185 
2186                 /* Backspace? */
2187                 if (key == '\b' && ctx->n) {
2188                         cmdwin_pop();
2189                         ctx->ptr--;
2190                         *ctx->ptr = 0;
2191                         ctx->n--;
2192 
2193                         /* Back to empty? Re-prompt. */
2194                         if (!ctx->n) {
2195                                 cmdwin_spush(ctx->prompt);
2196                                 ctx->state = GN_ZERO;
2197                         }
2198                         return 0;
2199                 }
2200 
2201                 /* Out of space? */
2202                 if (ctx->n == MAX_WORDS_IN_SPELL_NAME) {
2203                         return 0;
2204                 }
2205 
2206                 /* Not a letter? */
2207                 if (!isalpha(key))
2208                         return 0;
2209 
2210                 /* Lookup the word that goes with the letter. */
2211                 letter = toupper(key);
2212                 word = magic_lookup_word(&Session->magic, letter);
2213                 if (!word) {
2214                         return 0;
2215                 }
2216 
2217                 /* Accept the word and print it. After the first word separate words
2218                  * with a space. */
2219                 cmdwin_push(" %s", word);
2220                 *ctx->ptr = letter;
2221                 ctx->ptr++;
2222                 ctx->n++;
2223                 return 0;
2224 
2225         default: /* Invalid state */
2226                 assert(0);
2227                 return 0;
2228         }
2229 
2230 }
2231 
select_spell(struct get_spell_name_data * context)2232 int select_spell(struct get_spell_name_data *context)
2233 {
2234 	struct KeyHandler kh;
2235 
2236 	memset(context, 0, sizeof(*context));
2237 	context->ptr = context->spell_name;
2238         context->prompt = "<spell name>";
2239         context->state = GN_ZERO;
2240 
2241 	kh.fx = get_spell_name;
2242 	kh.data = context;
2243 
2244         cmdwin_spush(context->prompt);
2245 	eventPushKeyHandler(&kh);
2246 	eventHandle();
2247 	eventPopKeyHandler();
2248 
2249 	if (strlen(context->spell_name) == 0) {
2250 		cmdwin_spush("none!");
2251 		return -1;
2252 	}
2253 
2254 	return 0;
2255 }
2256 
2257 /**
2258  * Log console messages describing the results of a closure call and return
2259  * whether or not it succeeded.
2260  *
2261  * @param result is what closure_exec() returned. It should be one of the
2262  * standard result codes.
2263  *
2264  * @returns non-zero iff the closure call was successful. "Succesful" means it
2265  * actually did something; whereas "unsuccesful" means the request was
2266  * effectively aborted in the script, so the caller should skip post-processing
2267  * like mp or ap reduction, etc.
2268  */
cmd_eval_and_log_result(int result)2269 static int cmd_eval_and_log_result(int result)
2270 {
2271         static struct {
2272                 const char *string;
2273                 int success;
2274         } tbl[] = {
2275                 { "^c+gok^c-!",               1 },
2276                 { "^c+Gno target^c-!",        0 },
2277                 { "^c+yno effect^c-!",        1 },
2278                 { "^c+yno hostiles here^c-!", 1 },
2279                 { "^c+Glacks skill^c-!",      0 },
2280                 { "^c+rfailed^c-!",           1 },
2281                 { "^c+Gnot here^c-!",         0 },
2282                 { "^c+rcritical fail^c-!!!",  1 },
2283                 { "^c+ynot now^c-!",          0 },
2284         };
2285 
2286         if (result < 0 || result >= array_sz(tbl)) {
2287                 warn("result code '%d' unknown\n", result);
2288                 return 1;
2289         }
2290 
2291         log_continue(tbl[result].string);
2292         return tbl[result].success;
2293 }
2294 
cmdCastSpell(class Character * pc)2295 bool cmdCastSpell(class Character * pc)
2296 {
2297 	struct get_spell_name_data context;
2298 	struct inv_entry *ie = NULL;
2299 	struct spell *spell;
2300 	bool mixed = false;
2301 	bool natural = false;
2302 	int i, cast = 0, result = 0;
2303         char spell_name[MAX_SPELL_NAME_LENGTH];
2304 
2305         if (MagicNegated) {
2306                 log_msg("Cast - magic negated!\n");
2307                 return false;
2308         }
2309 
2310 	cmdwin_clear();
2311 	cmdwin_spush("Cast");
2312 
2313 	/* If the pc is null then we are in non-combat mode and need to promp
2314          * the user. */
2315 	if (pc == NULL) {
2316 		pc = select_party_member();
2317 		if (pc == NULL) {
2318 			return false;
2319 		}
2320 		statusSetMode(ShowParty);
2321 	}
2322 
2323         /* Make sure the PC is not asleep, dead, etc. */
2324         if (pc->isDead()) {
2325                 cmdwin_spush("unable right now!");
2326                 log_msg("Cast - %s is too dead!", pc->getName());
2327                 return false;
2328         }
2329 
2330         if (pc->isAsleep()) {
2331                 cmdwin_spush("unable right now!");
2332                 log_msg("Cast - %s is asleep!", pc->getName());
2333                 return false;
2334         }
2335 
2336 	/* Prompt to select a spell */
2337 	if (select_spell(&context) == -1)
2338 		return false;
2339 
2340         /* The code for the spell is stored in the context, but not the full
2341          * name. I want the full name for log msgs. */
2342         magic_spell_code_to_name(&Session->magic, spell_name,
2343                                  MAX_SPELL_NAME_LENGTH,
2344                                  context.spell_name);
2345 
2346         log_begin("%s: %s - ", pc->getName(), spell_name);
2347 
2348 	/* Lookup the spell in the list of valid spells. */
2349 	spell = magic_lookup_spell(&Session->magic, context.spell_name);
2350 	if (!spell) {
2351                 /* Bugfix for SF1564255: don't let player guess at spells. */
2352 		cmdwin_spush("none mixed!");
2353                 log_end("none mixed!");
2354 		return false;
2355 	}
2356 
2357 	/* Check if the spell can be used in this context. */
2358 	if (!(player_party->getContext() & spell->context)) {
2359 		cmdwin_spush("not here!");
2360                 log_end("not here!");
2361 		return false;
2362 	}
2363 
2364 	/* Check if the character comes by this spell naturally. */
2365 	for (i = 0; i < pc->species->n_spells; i++) {
2366 		if (! strcmp(pc->species->spells[i], spell->code)) {
2367 			natural = true;
2368 			break;
2369 		}
2370 	}
2371 
2372         /* Check if the caster is of sufficient level. */
2373         /*
2374          * FIXME: what if the spell is natural? cast An Xen Exe on a snake and
2375          * try to cast In Nox Por to see what I mean...
2376          */
2377 	if (!natural && pc->getLevel() < spell->level) {
2378 		cmdwin_spush("need more experience!");
2379                 log_end("must be level %d!", spell->level);
2380 		return false;
2381 	}
2382 
2383 	/* Check party inventory for a mixed spell. */
2384 	if (!natural) {
2385 		ie = player_party->inventory->search(spell->type);
2386 		if (ie && ie->count)
2387 			mixed = true;
2388 	}
2389 
2390 	if (!natural && !mixed) {
2391 		cmdwin_spush("none mixed!");
2392                 log_end("none mixed!");
2393 		return false;
2394 	}
2395 
2396 	/* Check if the character has enough mana to cast the spell. */
2397 	if (pc->getMana() < spell->cost) {
2398 		cmdwin_spush("need more mana!");
2399                 log_end("need more mana!");
2400 		return false;
2401 	}
2402 
2403         /* Cast the spell. */
2404         result = spell->type->cast(pc);
2405         cast = cmd_eval_and_log_result(result);
2406 
2407         if (! cast) {
2408                 log_end(NULL);
2409                 return false;
2410         }
2411 
2412         /* Decrement the caster's mana. */
2413         pc->runHook(OBJ_HOOK_CAST_DONE, 0);
2414         pc->addMana(0 - spell->cost);
2415         pc->decActionPoints(spell->action_points);
2416         pc->addExperience(spell->cost);
2417 
2418 	/* If the spell was mixed then remove it from inventory. */
2419 	if (mixed) {
2420                 int count = ie->count - 1;
2421 		player_party->takeOut(ie->type, 1);
2422                 log_msg("%d %s remaining", count, spell_name);
2423         }
2424 
2425         /* Some spells have status in the foogod window, so repaint it now. */
2426         foogodRepaint();
2427 
2428         log_end(NULL);
2429 
2430 	return true;
2431 
2432 }
2433 
cmdMixReagents(class Character * character)2434 bool cmdMixReagents(class Character *character)
2435 {
2436 	struct spell *spell;
2437 	struct get_spell_name_data context;
2438 	struct list reagents, *elem;
2439 	int quantity, max_quantity;
2440 	struct inv_entry *ie, *ie_spell = 0;
2441 	bool mistake = false;
2442         char spell_name[MAX_SPELL_NAME_LENGTH];
2443 
2444 	list_init(&reagents);
2445 
2446 	cmdwin_clear();
2447 	cmdwin_spush("Mix");
2448 
2449 	// Select a spell...
2450 	if (select_spell(&context) == -1)
2451 		return false;
2452 
2453         // The code for the spell is stored in the context, but not the full
2454         // name. I want the full name for log msgs.
2455         magic_spell_code_to_name(&Session->magic, spell_name,
2456                                  MAX_SPELL_NAME_LENGTH,
2457                                  context.spell_name);
2458 
2459 	// Lookup the spell. If null then keep going and bomb when done.
2460 	spell = magic_lookup_spell(&Session->magic, context.spell_name);
2461 
2462         // Show the player how many he already has mixed...
2463         ie_spell = 0;
2464         if (spell) {
2465                 ie_spell = player_party->inventory->search(spell->type);
2466         }
2467         if (ie_spell && ie_spell->count) {
2468                 cmdwin_spush("%d mixed", ie_spell->count);
2469         } else {
2470                 cmdwin_spush("0 mixed");
2471         }
2472 
2473 	// Prompt for reagents
2474 	cmdwin_spush("<select, then M)ix>");
2475 
2476         foogodSetHintText("\005\006=scroll ENT=add/remove ESC=abort M=done");
2477         foogodSetMode(FOOGOD_HINT);
2478 
2479 	// Show the reagents in the status window
2480 	statusSetMode(MixReagents);
2481 
2482 	struct ScrollerContext sc;
2483 	sc.selector = Reagents;
2484 	sc.done = false;
2485 	sc.abort = false;
2486 	sc.mixing = true;
2487 
2488 	struct KeyHandler kh;
2489 	kh.fx = scroller;
2490 	kh.data = &sc;
2491 
2492 	eventPushKeyHandler(&kh);
2493 
2494 	for (;;) {
2495 		sc.selection = NULL;
2496 		eventHandle();
2497 
2498 		if (sc.abort) {
2499 			// u5 silently aborts here
2500                         cmdwin_pop();
2501 			eventPopKeyHandler();
2502 			cmdwin_spush("none!");
2503 			goto done;
2504 		}
2505 
2506 		if (sc.done)
2507 			break;
2508 
2509 		ie = (struct inv_entry *) sc.selection;
2510                 if (! ie) {
2511                         /* This happens when the player has no reagents
2512                          * whatsoever. */
2513 			cmdwin_pop();
2514 			eventPopKeyHandler();
2515 			cmdwin_spush("none!");
2516 			goto done;
2517                 }
2518 
2519 		if (ie->ref) {
2520 			// unselect
2521 			ie->ref = 0;
2522 			list_remove(&ie->auxlist);
2523 		} else {
2524 			// select
2525 			ie->ref = 1;
2526 			list_add(&reagents, &ie->auxlist);
2527 		}
2528 
2529 		statusRepaint();
2530 	}
2531 
2532 	cmdwin_pop();
2533 	eventPopKeyHandler();
2534 
2535 	if (list_empty(&reagents)) {
2536 		cmdwin_spush("none!");
2537 		goto done;
2538 	}
2539 
2540 	// Determine the max number of mixtures the player can make.
2541 	max_quantity = 0x7fffff;
2542 	list_for_each(&reagents, elem) {
2543 		ie = outcast(elem, struct inv_entry, auxlist);
2544 		if (ie->count < max_quantity) {
2545 			max_quantity = ie->count;
2546 		}
2547 	}
2548 
2549 	// Prompt for the number of mixtures to make
2550 	for (;;) {
2551 
2552 		int dummy;
2553 
2554                 cmdwin_push_mark();
2555 		quantity = ui_get_quantity(max_quantity);
2556 
2557 		if (quantity == 0) {
2558 			goto done;
2559 		}
2560 
2561 		if (quantity <= max_quantity)
2562 			break;
2563 
2564                 cmdwin_spush(0); /* for the '-' after the quantity */
2565 		cmdwin_spush("not enough reagents!");
2566 		getkey(&dummy, anykey);
2567 		cmdwin_pop_to_mark();
2568 	}
2569 
2570         cmdwin_push("-");
2571 	log_begin("Mix: %s - ", spell_name);
2572 
2573 	// For each reagent required by the spell, check if it is in the list
2574 	// of reagents given by the player. If not then remember this fact. If
2575 	// the reagent is found then remove it from player inventory and remove
2576 	// it from the list.
2577 	if (spell) {
2578 		for (int i = 0; i < spell->n_reagents; i++) {
2579 			bool found = false;
2580 			list_for_each(&reagents, elem) {
2581 				ie = outcast(elem, struct inv_entry, auxlist);
2582 				if (ie->type ==
2583 				    (class ObjectType *) spell->reagents[i]) {
2584                                         // The following line is safe only
2585 					// because this is the end of the
2586 					// list_for_each loop!
2587 					list_remove(elem);
2588 					ie->ref--;
2589 					player_party->takeOut(ie->type,
2590                                                               quantity);
2591 					found = true;
2592 					break;
2593 				}
2594 			}
2595 			if (!found)
2596 				mistake = true;
2597 		}
2598 	}
2599 
2600 	// Now, if any reagents remain leftover then remember this fact and
2601 	// remove the remaining reagents from inventory.
2602 	if (!list_empty(&reagents)) {
2603 		mistake = true;
2604 		elem = reagents.next;
2605 		while (elem != &reagents) {
2606 			struct list *tmp = elem->next;
2607 			ie = outcast(elem, struct inv_entry, auxlist);
2608 			list_remove(elem);
2609 			elem = tmp;
2610 			ie->ref--;
2611 			player_party->takeOut(ie->type, quantity);
2612 		}
2613 	}
2614 
2615         statusSetMode(ShowParty);
2616         foogodSetMode(FOOGOD_DEFAULT);
2617 
2618         // committed to action now, so decrement AP
2619         if (character) {
2620                 character->runHook(OBJ_HOOK_MIX_DONE, 0);
2621 
2622                 if (!spell)
2623                 {
2624 		    // Failed attempt (mixing a spell which does not exist)
2625 		    int num  = kern_intvar_get("AP_COST:mix_reagents_nospell_num");
2626 		    int dice = kern_intvar_get("AP_COST:mix_reagents_nospell_dice");
2627 		    int plus = kern_intvar_get("AP_COST:mix_reagents_nospell_plus");
2628 		    int AP_wasted = dice_roll_numeric(num, dice, plus);
2629 		    character->decActionPoints(AP_wasted);
2630 		    // was 3d50+20 -- dice_roll_numeric(3, 50, 20)
2631                 }
2632                 else if (mistake)
2633                 {
2634 		    int level = spell->level;
2635 		    int num  = kern_intvar_get("AP_COST:mix_reagents_badmix_num");
2636 		    int dice = kern_intvar_get("AP_COST:mix_reagents_badmix_dice");
2637 		    int plus = kern_intvar_get("AP_COST:mix_reagents_badmix_plus");
2638 		    int AP_wasted = dice_roll_numeric(level * num, dice, plus);
2639 		    character->decActionPoints(AP_wasted);
2640 		    // was 1d(2*AP)+100 -- dice_roll_numeric(1 ,(2 * spell->action_points) , 100)
2641             	 }
2642             	else
2643             	{
2644 		    // mixing should be SLOW
2645 		    int base      = kern_intvar_get("AP_COST:mix_reagents_base");
2646 		    int per_mix   = kern_intvar_get("AP_COST:mix_reagents_per_mix");
2647 		    int per_level = kern_intvar_get("AP_COST:mix_reagents_per_level");
2648 		    int AP_spent  = base + (quantity * per_mix) + (spell->level * per_level);
2649 		    character->decActionPoints(AP_spent);
2650 		    // was 100 + 2 * spell->action_points
2651             	}
2652         }
2653 
2654 	// If the spell is invalid or the reagents are incorrect then punish
2655 	// the player.
2656 	if (!spell) {
2657                 cmdwin_spush("oops!");
2658                 player_party->damage(DAMAGE_ACID);
2659                 log_end("ACID!");
2660                 goto done;
2661 
2662         } else if (mistake) {
2663                 cmdwin_spush("ouch!");
2664                 player_party->damage(DAMAGE_BOMB);
2665                 log_end("BOMB!");
2666                 goto done;
2667 	}
2668 
2669 	// All is well. Add the spell to player inventory.
2670         cmdwin_spush("ok");
2671 	player_party->add(spell->type, quantity);
2672         log_end("ok!");
2673 
2674  done:
2675 	// In case of cancellation I need to unselect all the reagents.
2676 	elem = reagents.next;
2677 	while (elem != &reagents) {
2678 		struct list *tmp = elem->next;
2679 		ie = outcast(elem, struct inv_entry, auxlist);
2680 		list_remove(elem);
2681 		elem = tmp;
2682 		ie->ref--;
2683 	}
2684 	statusSetMode(ShowParty);
2685         foogodSetMode(FOOGOD_DEFAULT);
2686 	return true;
2687 }
2688 
look_at_XY(struct place * place,int x,int y,void * unused)2689 void look_at_XY(struct place *place, int x, int y, void *unused)
2690 {
2691         if (DeveloperMode) {
2692                 log_begin("At XY=(%d,%d): ", x, y);
2693         } else {
2694                 log_begin("");
2695         }
2696 
2697         if ( mapTileIsVisible(x, y) ) {
2698                 if (mapTileLightLevel(x,y) < MIN_XAMINE_LIGHT_LEVEL) {
2699                         log_continue("Too dark!");
2700                 } else {
2701                         log_continue("You see ");
2702                         place_describe(place, x, y, PLACE_DESCRIBE_ALL);
2703                 }
2704         } else if (ShowAllTerrain || XrayVision) {
2705                 log_continue("You see (via xray) ");
2706                 place_describe(place, x, y, PLACE_DESCRIBE_TERRAIN);
2707         } else {
2708                 log_continue("You can't see!");
2709         }
2710 
2711         log_end(NULL);
2712 }
2713 
detailed_examine_XY(struct place * place,int x,int y,void * unused)2714 int detailed_examine_XY(struct place *place, int x, int y, void *unused)
2715 {
2716 	if (DeveloperMode) {
2717 			log_begin("At XY=(%d,%d): ", x, y);
2718 	} else {
2719 			log_begin("");
2720 	}
2721 
2722 	if ( mapTileIsVisible(x, y) ) {
2723 			if (mapTileLightLevel(x,y) < MIN_XAMINE_LIGHT_LEVEL) {
2724 					log_continue("You can't see!");
2725 			} else {
2726 					log_continue("You see:\n");
2727 					place_examine(place, x, y);
2728 			}
2729 	} else if (ShowAllTerrain || XrayVision) {
2730 			log_continue("You see (via xray):\n");
2731 			place_examine(place, x, y);
2732 	} else {
2733 			log_continue("You can't see!");
2734 	}
2735 
2736 	#if 0
2737 	// SAM:
2738 	// Hmmm...how best to print more info about
2739 	// the objects on this tile?
2740         if ( mapTileIsVisible(x, y) ) {
2741                 log_msg("DETAIL XY=(%d,%d) TODO - print detailed view\n", x, y);
2742                 // For each object/terrain on the tile, print the name (and
2743                 // perhaps show the sprite in a Status Window mode), and also
2744                 // show:
2745                 //
2746                 //     o whether this object blocks LOS (alpha)
2747                 //     o whether this object blocks movement (pmask)
2748                 //     o whether this object causes some effect when stepped
2749                 //       upon (hazardous terrain effects, pressure plate
2750                 //       triggers)
2751                 //     o information specific to the object type, such as:
2752                 //     o Triggers: current state, and perhaps what it is
2753                 //       connected to?
2754                 //     o NpcParties: faction, movement mode
2755                 //       (pmask), ...
2756                 //     o Vehicles: movement mode, armament, current HP
2757                 //     o Portable items: weapon/armor stats, (U)se effects,
2758                 //       etc...
2759                 // Hmmm...what else?
2760                 return;
2761         }
2762         log_msg("DETAIL XY=(%d,%d) out of LOS\n", x, y);
2763 	#endif
2764 
2765 	log_end(NULL);
2766 
2767         return 0; /* keep on targeting */
2768 }
2769 
cmdXamine(class Object * pc)2770 bool cmdXamine(class Object * pc)
2771 {
2772 	// SAM: Working on an improved (L)ook command,
2773 	// which works as a "Look Mode" rather than a
2774 	// "look at 1 tile" command...
2775 	int x, y;
2776         bool ret = true;
2777 
2778 	cmdwin_clear();
2779 	cmdwin_spush("Xamine");
2780 
2781         x = pc->getX();
2782         y = pc->getY();
2783 
2784         log_begin_group();
2785 
2786         if (pc)
2787                 log_msg("%s examines around...", pc->getName());
2788         else
2789                 log_msg("You examine around...");
2790 
2791         look_at_XY(pc->getPlace(), x, y, 0);  // First look at the current tile
2792 	if (select_target_with_doing(x, y, &x, &y, pc->getVisionRadius(),
2793 				     look_at_XY, detailed_examine_XY) == -1) {
2794 		ret = false;
2795 	}
2796 
2797         log_end_group();
2798 
2799 	return ret;
2800 } // cmdXamine()
2801 
name_of_context(void)2802 const char * name_of_context (void)
2803 {
2804         // SAM: Perhaps this function belongs in common.c?
2805         switch (player_party->getContext()) {
2806         case CONTEXT_WILDERNESS:
2807                 return "Party Context";
2808                 break;
2809         default:
2810                 return "Character Context";
2811                 break;
2812         }
2813 } // name_of_context()
2814 
cmdAT(class Character * pc)2815 bool cmdAT (class Character * pc)
2816 {
2817 	int x, y;
2818         const char * who = "";
2819         const char * place_name = "";
2820 
2821 	cmdwin_clear();
2822 
2823         // Should I check player_party->context
2824         // for the context info below,
2825         // rather than the current method?
2826 	if (pc) {
2827 		// A party member was specified as a parameter, so this must be
2828 		// combat mode. Use the party member's location as the origin.
2829                 who = pc->getName();
2830                 place_name =  Place->name;
2831 		x = pc->getX();
2832 		y = pc->getY();
2833 	}
2834         else {
2835 		// Must be party mode.
2836 		// Use the player party's location as the origin.
2837                 who = "The party";
2838                 place_name = player_party->getPlace()->name;
2839                 x = player_party->getX();
2840                 y = player_party->getY();
2841 	}
2842         // SAM: Why is this line not safe in combat mode?
2843         //      Would it be The Right Thing (TM)
2844         //      for it to be made safe in all contexts?
2845         // place_name = player_party->getPlace()->name;
2846 
2847         log_begin_group();
2848         log_msg("This is %s.", name_of_context() );
2849         log_msg("%s is in %s.", who, place_name);
2850 		if (Place->underground) {
2851 			log_msg("It is %s, %s of %s in the year %d.",
2852                 day_name(), week_name(), month_name(), Session->clock.year );
2853         }
2854 		else
2855 		{
2856 			log_msg("It is %s on %s, "
2857                 "%s of %s in the year %d.",
2858                 vague_time_as_string(), day_name(),
2859                 week_name(), month_name(), Session->clock.year );
2860 		}
2861         // SAM: Is this really interesting though, I wonder?
2862         log_msg("%d game turns have passed.", Turn);
2863 
2864         log_msg("The wind is blowing from the %s.",
2865                 directionToString(windGetDirection()) );
2866 
2867         if (Place->underground) {
2868                 log_msg("%s is underground, and cannot see the sky.",
2869                         who);
2870         } // underground
2871         else {
2872                 struct list *elem;
2873 
2874                 // SAM:
2875                 // This message won't be true if you are under
2876                 // a roof in a town.  In future there should be
2877                 // logic querying the (future) roof-ripping code here.
2878                 log_msg("%s is beneath the open sky.", who);
2879 
2880                 // The kernel no longer has any special knowledge about which
2881                 // astral body is the sun, so we have to deal with all astral
2882                 // bodies generically now. I mean, a game may have two or even
2883                 // more suns. The time runs independently and isn't cued off
2884                 // the sun.
2885                 if (is_noon())
2886                         log_msg("It is noon.");
2887                 else if (is_midnight())
2888                         log_msg("It is midnight.");
2889 
2890                 // Report on each astral body generically.
2891                 list_for_each(&Session->sky.bodies, elem) {
2892                         struct astral_body *body;
2893                         body = outcast(elem, struct astral_body, list);
2894                         if (astral_body_is_visible(body->arc)) {
2895                                 log_begin("%s is up at arc %d", body->name,
2896                                         body->arc);
2897                                 if (body->n_phases > 1) {
2898                                         char *phase_name =
2899                                                 body->phases[body->phase].
2900                                                 name;
2901                                         if (phase_name)
2902                                                 log_continue(" in its %s "
2903                                                              "phase",
2904                                                              phase_name);
2905                                         else
2906                                                 log_continue(" in phase %d",
2907                                                              body->phase);
2908                                 }
2909                                 log_end(".");
2910                         }
2911                 }
2912 
2913         } // open air, under the sky
2914 
2915         if (player_party->getVehicle()) {
2916                 log_msg("%s is %s a %s.",
2917                         who, "using", player_party->getVehicle()->getName() );
2918                 // SAM:
2919                 // In future, we shall want GhulScript to specify
2920                 // whether one is to be
2921                 //     "riding" "driving" "piloting" "sailing"
2922                 // a particular vehicle type.
2923                 // The existing 'mv_desc' field (Ride, Sail)
2924                 // is related, but we need the gerund of the verb.
2925         }
2926         else {
2927                 // SAM: Not true for a party of Gazers or Nixies.
2928                 // Similar GhulScript for party / character movement mode
2929                 // descriptions and gerunds?
2930                 log_msg("%s is on foot.", who);
2931         }
2932 
2933         log_end_group();
2934 
2935         /*
2936           Information reported shall include:
2937           X the current place,X,Y
2938           X the in-game time in game time units (HH:MM, YYYY/MM/DD)
2939           X the in-game time in elapased turns (this is of less interest)
2940           X the current weather status (wind)
2941           X the current astronomy (day/night, sun, moons)
2942           X the UI mode of the party (Wilderness,Town,Dungeon,Combat)
2943           X whether the party is on foot or in a vehicle (and what type)
2944           . the number of party members, names, order, and basic vital stats
2945           . global party stats such as food and gold
2946           . any special effects (buffs/nerfs) currently affecting the party,
2947           . such as haste/quickness/slow, time stop, protection, etc.
2948           . Xamine type information about the tile the party is standing on.
2949         */
2950 
2951         return true;
2952 } // cmdAT()
2953 
2954 /**
2955  * cmd_terraform - edit terrain interactively
2956  */
cmd_terraform(struct place * place,int x,int y)2957 bool cmd_terraform(struct place *place, int x, int y)
2958 {
2959     terrain_editor_run(place, x, y);
2960     return true;
2961 }
2962 
cmd_save_current_place(struct place * place)2963 bool cmd_save_current_place (struct place * place)
2964 {
2965     FILE *   file;
2966     const char *   file_path;
2967     save_t * save;
2968     int ret;
2969 
2970     //log_msg("cmd_save_current_place");
2971     //printf("cmd_save_current_place()\n");
2972 
2973     file_path = "_test_save_place";
2974 
2975     file = file_open_in_save_dir(file_path, "w");
2976     if (file == NULL) {
2977 	log_msg("Save place to file '%s' failed.", file_path);
2978 	printf("Error on fopen() for file '%s': '%s'\n", file_path, strerror(errno));
2979 	return 0;
2980     }
2981     Session->session_id++;  // Must increment to cause saving.
2982 
2983     save = save_new(file);
2984     save->indent     = 0;
2985     save->session_id = Session->session_id;
2986 
2987     //terrain_map_print(file, 0, place->terrain_map);
2988     terrain_map_save(save, place->terrain_map);
2989     log_msg("Saved map to file:\n'%s'", file_path);
2990     printf( "Saved map to file '%s'\n", file_path);
2991 
2992     save_del(save);
2993 
2994     ret = fclose(file);
2995     if (ret != 0) {
2996 	// SAM: Not sure what we can do about it,
2997 	//      and this seems kind of low-level to log_msg() about...
2998 	printf("Error on fclose() for file '%s': '%s'\n", file_path, strerror(errno));
2999 	// It seems that the save method should return non-void,
3000 	// so that we know success/failure in this and other cases...
3001 	return 0;
3002     }
3003     return 1;
3004 }
3005 
cmdZoomIn(void)3006 void cmdZoomIn(void)
3007 {
3008         struct place *subplace = 0;
3009         //
3010         // SAM: Curently no "Enter" message is printed.  It turns out to be
3011         // moderately complicated to do so properly, as a distinct message for
3012         // each enter_combat() case might be desired...
3013         //
3014         // For now, I print a placeholder message for each case here:
3015 
3016         if ((subplace = place_get_subplace(player_party->getPlace(),
3017                                                   player_party->getX(),
3018                                                   player_party->getY()))) {
3019                 if (ENABLE_TOWN_ZOOM_IN) {
3020                         // Standing over a subplace. Try to enter with no
3021                         // direction, this will prompt the player to provide a
3022                         // direction.
3023                         log_msg("Enter-%s", subplace->name);
3024                         player_party->try_to_enter_subplace_from_edge(subplace,
3025                                                                       0, 0);
3026                 } else {
3027                         log_msg("Enter-Use a side entrance!");
3028                 }
3029 
3030         } else if (!place_is_passable(player_party->getPlace(),
3031                                       player_party->getX(),
3032                                       player_party->getY(),
3033                                       player_party,
3034                                       PFLAG_IGNOREVEHICLES)) {
3035 
3036                 // Currently zooming in to impassable terrain is not doable;
3037                 // since the party might not be placeable on the default map,
3038                 // which would be a solid grid of that impassable terrain.
3039                 // Also, if somehow placed, the party could not escape unless
3040                 // on an edge.  There would be no harm in it otherwise,
3041                 // however.
3042                 struct terrain * tt =
3043                         place_get_terrain(player_party->getPlace(),
3044                                           player_party->getX(),
3045                                           player_party->getY() );
3046                 log_msg("Enter-Cannot zoom-in to %s!", tt->name);
3047         } else {
3048                 // If standing on ordinary terrain, zoom in:
3049                 struct terrain * tt =
3050                         place_get_terrain(player_party->getPlace(),
3051                                           player_party->getX(),
3052                                           player_party->getY() );
3053                 log_msg("Enter-%s", tt->name);
3054                 run_combat(false, 0, 0, NULL);
3055         }
3056 }
3057 
cmdSave(void)3058 bool cmdSave(void)
3059 {
3060     bool ret = true;
3061     class Character *pc;
3062     if ((pc = cmdAnyPartyMemberEngagedInTask())) {
3063         log_msg("Denied - %s engaged in task!", pc->getName());
3064         cmdwin_spush("busy with tasks!");
3065         return false;
3066     }
3067 
3068     char *fname = save_game_menu();
3069     if (!fname) {
3070         cmdwin_spush("abort!");
3071         return false;
3072     }
3073 
3074     log_begin("Saving to %s...", fname);
3075     if (session_save(fname)) {
3076         log_end("^c+rfailed!^c-");
3077         cmdwin_spush("failed!");
3078         ret = false;
3079     } else {
3080         cmdwin_spush("ok!");
3081         log_end("^c+gok!^c-");
3082     }
3083     session_save(fname);
3084     free(fname);
3085     return ret;
3086 }
3087 
cmdReload(void)3088 void cmdReload(void)
3089 {
3090         Reload = 1;
3091 }
3092 
3093 /****** New UI ******/
3094 
3095 #define MARKUP 4
3096 
3097 
ui_get_yes_no(const char * name)3098 int ui_get_yes_no(const char *name)
3099 {
3100 	int yesno;
3101 	cmdwin_clear();
3102 	cmdwin_spush("Reply");
3103         cmdwin_spush("<y/n>");
3104 	getkey(&yesno, yesnokey);
3105 	cmdwin_pop();
3106 	if (yesno == 'y') {
3107 		cmdwin_spush("yes");
3108 		log_msg("^c+%c%s:^c- Yes", CONV_PC_COLOR, name);
3109                 return 1;
3110 	} else {
3111 		cmdwin_spush("no");
3112 		log_msg("^c+%c%s:^c- No", CONV_PC_COLOR, name);
3113                 return 0;
3114 	}
3115 }
3116 
3117 typedef struct ui_getline_data {
3118         char *ptr;
3119         char *buf;
3120         int room;
3121         int (*filter)(int key);
3122 } getline_t;
3123 
ui_getline_handler(struct KeyHandler * kh,int key,int keymod)3124 static int ui_getline_handler(struct KeyHandler *kh, int key, int keymod)
3125 {
3126         getline_t *data = (getline_t*)kh->data;
3127 
3128 	if (key == CANCEL) {
3129 		while (data->ptr > data->buf) {
3130 			data->ptr--;
3131 			*data->ptr = 0;
3132 			cmdwin_pop();
3133 			data->room++;
3134 		}
3135 		return 1;
3136 	}
3137 
3138 	if (key == '\n') {
3139 		return 1;
3140 	}
3141 
3142 	if (key == '\b') {
3143 		if (data->ptr != data->buf) {
3144 			data->ptr--;
3145 			*data->ptr = 0;
3146 			data->room++;
3147 			cmdwin_pop();
3148 		}
3149 		return 0;
3150 	}
3151 
3152         if (data->filter
3153             && data->filter(key)) {
3154                 return 0;
3155         }
3156 
3157 	if (isprintable(key)
3158             && data->room) {
3159 		cmdwin_push("%c", key);
3160 		*data->ptr++ = key;
3161 		data->room--;
3162 	}
3163 
3164 	return 0;
3165 }
3166 
ui_getline_filtered(char * buf,int len,int (* filter)(int key))3167 int ui_getline_filtered(char *buf, int len, int (*filter)(int key))
3168 {
3169         struct KeyHandler kh;
3170         getline_t data;
3171 
3172         data.buf  = buf;
3173         data.ptr  = buf;
3174         data.room = len - 1;
3175         data.filter = filter;
3176 
3177         memset(buf, 0, len);
3178 
3179         kh.fx   = ui_getline_handler;
3180         kh.data = &data;
3181 
3182         eventPushKeyHandler(&kh);
3183         eventHandle();
3184         eventPopKeyHandler();
3185 
3186         return len - (data.room + 1);
3187 }
3188 
ui_getline_plain(char * buf,int len)3189 int ui_getline_plain(char *buf, int len)
3190 {
3191         return ui_getline_filtered(buf, len, 0);
3192 }
3193 
ui_getline(char * buf,int len)3194 int ui_getline(char *buf, int len)
3195 {
3196         cmdwin_clear();
3197         cmdwin_push("Say: ");
3198         return ui_getline_plain(buf, len);
3199 }
3200 
ui_buy(struct merchant * merch)3201 int ui_buy(struct merchant *merch)
3202 {
3203 	struct KeyHandler kh;
3204 	struct ScrollerContext sc;
3205 	struct trade_info *trade;
3206 	int quantity, cost, max_q, bought = 0;
3207 
3208 	statusSetTradeInfo(merch->n_trades, merch->trades);
3209 	statusSetMode(Trade);
3210 
3211 	sc.selector = TradeItem;
3212 	kh.fx = scroller;
3213 	kh.data = &sc;
3214 
3215 	for (;;) {
3216 
3217 		// *** selection ***
3218 
3219 		sc.selection = NULL;
3220 
3221 		cmdwin_clear();
3222 		cmdwin_spush("Buy");
3223                 cmdwin_spush("<select/ESC>");
3224 		eventPushKeyHandler(&kh);
3225 		eventHandle();
3226 		eventPopKeyHandler();
3227 		cmdwin_pop();
3228 
3229 		trade = (struct trade_info *) sc.selection;
3230 
3231 		if (!trade) {
3232 			cmdwin_spush("none!");
3233 			break;
3234 		}
3235 
3236                 /* Print the sales pitch to the console, if one exists */
3237                 if (trade->sales_pitch) {
3238                         log_msg("^c+%c%s:^c- %s", CONV_NPC_COLOR, merch->name,
3239                                 trade->sales_pitch);
3240                 }
3241 
3242 		cmdwin_spush("%s", trade->name);
3243 
3244 		if (player_party->gold < trade->cost) {
3245 			int dummy;
3246 			cmdwin_spush("not enough gold! <hit any key>");
3247 			getkey(&dummy, anykey);
3248 			continue;
3249 		}
3250 		// *** quantity ***
3251 
3252                 cmdwin_push_mark();
3253 		max_q = player_party->gold / trade->cost;
3254 		quantity = ui_get_quantity(max_q);
3255                 cmdwin_pop_to_mark();
3256 
3257 		if (quantity == 0) {
3258 			cmdwin_spush("none!");
3259 			continue;
3260 		}
3261 
3262 		quantity = min(quantity, max_q);
3263 		cmdwin_spush("%d", quantity);
3264 
3265 		cost = quantity * trade->cost;
3266 
3267 		// *** trade ***
3268 
3269                 class ObjectType *type = (class ObjectType*)trade->data;
3270 		cmdwin_spush("ok");
3271 		log_msg("You buy %d %s%s for %d gold\n", quantity,
3272 			     trade->name, quantity > 1 ? "s" : "", cost);
3273 
3274 		player_party->gold -= cost;
3275                 if (type->canBuy()) {
3276                         type->buy(player_party->get_leader(), quantity);
3277                 } else {
3278                         player_party->add(type, quantity);
3279                 }
3280                 trade->quantity = player_party->inventory->numAvail(type);
3281                 statusRepaint();
3282 		foogodRepaint();
3283                 bought++;
3284 	}
3285 
3286 	statusSetMode(ShowParty);
3287         return bought;
3288 }
3289 
conv_filter_trade(struct inv_entry * ie,void * fdata)3290 static bool conv_filter_trade(struct inv_entry *ie, void *fdata)
3291 {
3292         struct trade_info *trade = (struct trade_info*)fdata;
3293         return (ie->type == trade->data && ie->count > ie->ref);
3294 }
3295 
fill_sell_list(struct merchant * merch,struct trade_info * trades)3296 static int fill_sell_list(struct merchant *merch, struct trade_info *trades)
3297 {
3298 	struct inv_entry *ie = NULL;
3299         struct filter filter;
3300 	int i, j = 0;
3301 
3302         filter.fx = conv_filter_trade;
3303 
3304         for (i = 0; i < merch->n_trades; i++) {
3305 
3306                 if (merch->trades[i].cost / MARKUP == 0)
3307                         continue;
3308 
3309                 filter.fdata = &merch->trades[i];
3310                 ie = player_party->inventory->first(&filter);
3311                 if (!ie)
3312                         continue;
3313 
3314                 /* Why don't aren't we setting show_sprite here, too? */
3315                 trades[j] = merch->trades[i];
3316                 trades[j].cost /= MARKUP;
3317                 trades[j].quantity = ie->count - ie->ref;
3318                 trades[j].show_quantity = 1;
3319                 j++;
3320         }
3321 
3322 	return j;
3323 }
3324 
ui_sell(struct merchant * merch)3325 int ui_sell(struct merchant *merch)
3326 {
3327 	// A bit trickier than the "Buy" scenario. A merchant will only buy
3328 	// items that it is willing to turn around and sell at a profit. When
3329 	// it comes time to select an item to sell the user should only see the
3330 	// list of items in player inventory which the merchant is willing to
3331 	// buy. So here we need to build that list and feed it to the status
3332 	// viewer.
3333 
3334 	int n_trades = 0;
3335 	struct trade_info *trades;
3336 	struct KeyHandler kh;
3337 	struct ScrollerContext sc;
3338 	struct trade_info *trade;
3339         int sold = 0;
3340 
3341 	// Allocate the trade list.
3342 	trades = new struct trade_info[merch->n_trades];
3343 	if (!trades) {
3344 		log_msg("^c+%c%s:^c- I don't need anything.\n",
3345                         CONV_NPC_COLOR, merch->name);
3346 		return 0;
3347 	}
3348 	// Fill out the list
3349 	n_trades = fill_sell_list(merch, trades);
3350 	statusSetTradeInfo(n_trades, trades);
3351 	statusSetMode(Trade);
3352 
3353 	sc.selector = TradeItem;
3354 	kh.fx = scroller;
3355 	kh.data = &sc;
3356 
3357 	for (;;) {
3358 
3359 		struct inv_entry *ie;
3360 		int quantity, max_q;
3361 
3362 		sc.selection = NULL;
3363 
3364 		cmdwin_clear();
3365 		cmdwin_spush("Sell");
3366                 cmdwin_spush("<select or ESC>");
3367 		eventPushKeyHandler(&kh);
3368 		eventHandle();
3369 		eventPopKeyHandler();
3370 		cmdwin_pop();
3371 
3372 		trade = (struct trade_info *) sc.selection;
3373 
3374 		if (!trade) {
3375 			cmdwin_spush("none!");
3376 			break;
3377 		}
3378 
3379 		cmdwin_spush("%s", trade->name);
3380 
3381 		ie = player_party->inventory->search((class ObjectType *)
3382                                                      trade->data);
3383 		assert(ie);
3384 		assert(ie->ref < ie->count);
3385 
3386 		// quantity
3387 
3388 		max_q = ie->count - ie->ref;
3389 
3390 		cmdwin_push_mark();
3391 		quantity = ui_get_quantity(max_q);
3392                 cmdwin_pop_to_mark();
3393 
3394 		if (quantity == 0) {
3395 			cmdwin_spush("none!");
3396 			continue;
3397 		}
3398 
3399 		quantity = min(quantity, max_q);
3400 		cmdwin_spush("%d", quantity);
3401 
3402 		// make the trade
3403 		player_party->takeOut(ie->type, quantity);
3404 		player_party->gold += quantity * trade->cost;
3405 		foogodRepaint();
3406 
3407 		cmdwin_spush("ok");
3408 		log_msg("You sell %d %s%s for %d gold\n", quantity,
3409 			     trade->name, quantity > 1 ? "s" : "",
3410 			     quantity * trade->cost);
3411 
3412 		// refresh the sell list
3413 		n_trades = fill_sell_list(merch, trades);
3414 		statusSetTradeInfo(n_trades, trades);
3415 		statusUpdateTradeInfo(n_trades, trades);
3416                 sold++;
3417 	}
3418 
3419 	statusSetMode(ShowParty);
3420 
3421 	delete trades;
3422         return sold;
3423 }
3424 
get_buy_or_sell_key(struct KeyHandler * kh,int key,int keymod)3425 static int get_buy_or_sell_key(struct KeyHandler *kh, int key, int keymod)
3426 {
3427 	int *val = (int *) kh->data;
3428 
3429 	switch (key) {
3430 	case 'b':
3431 	case 'B':
3432 		*val = 'b';
3433 		return 1;
3434 	case 's':
3435 	case 'S':
3436 		*val = 's';
3437 		return 1;
3438 	case CANCEL:
3439 		*val = 'x';
3440 		return 1;
3441 	default:
3442 		return 0;
3443 	}
3444 }
3445 
ui_trade(struct merchant * merch)3446 int ui_trade(struct merchant *merch)
3447 {
3448 	int key, traded = 0;
3449 
3450 	for (;;) {
3451 		cmdwin_clear();
3452 		cmdwin_spush("Buy or sell");
3453                 cmdwin_spush("<B/S/ESC>");
3454 		getkey(&key, get_buy_or_sell_key);
3455 
3456 		switch (key) {
3457 		case 'b':
3458 			traded += ui_buy(merch);
3459 			break;
3460 		case 's':
3461 			traded += ui_sell(merch);
3462 			break;
3463 		default:
3464 			cmdwin_pop();
3465 			cmdwin_spush("none!");
3466 			return traded;
3467 		}
3468 	}
3469 }
3470 
3471 static const char *cmd_help_text =
3472 "Use the arrow keys to indicate direction.\n"
3473 "Use the ESC key to cancel commands.\n"
3474 "Use the first letter to start a command.\n"
3475 "\n"
3476 "A)ttack something\n"
3477 "B)oard a ship or other vehicle\n"
3478 "C)ast a spell\n"
3479 "E)nter a town or dungeon\n"
3480 "F)ire a ship's cannon or other ordnance\n"
3481 "G)et something on the ground\n"
3482 "H)andle a lever or mechanism\n"
3483 "K)amp in a bed or the wilderness\n"
3484 "L)oiter for a while\n"
3485 "N)ew-Order (rearrange party order)\n"
3486 "O)pen a chest, door or other closed object\n"
3487 "Q)uit and save the game\n"
3488 "R)eady weapons or armor\n"
3489 "S)earch for hidden stuff\n"
3490 "T)alk to somebody\n"
3491 "U)se an item in inventory\n"
3492 "Z)tats (show party status)\n"
3493 "X)amine around\n"
3494 "@)AT (info about place & time)\n"
3495 "<space> (pass a turn)\n"
3496 "CTRL-Q)uit without saving\n"
3497 "CTRL-S)ave without quitting\n"
3498 "CTRL-R)eload the last saved game\n"
3499 "\n"
3500 "When talking to people, enter a keyword.\n"
3501 "Most people reply to NAME, JOB, TRADE and\n"
3502 "JOIN. Their replies will give you hints\n"
3503 "about more keywords.\n"
3504 ;
3505 
cmdHelp(void)3506 void cmdHelp(void)
3507 {
3508         struct KeyHandler kh;
3509 
3510         foogodSetHintText(PAGER_HINT);
3511         foogodSetMode(FOOGOD_HINT);
3512         statusSetPageText("Commands", cmd_help_text);
3513         statusSetMode(Page);
3514 
3515         kh.fx = scroller;
3516         kh.data = NULL;
3517 	eventPushKeyHandler(&kh);
3518 	eventHandle();
3519 	eventPopKeyHandler();
3520 
3521         statusSetMode(ShowParty);
3522         foogodSetMode(FOOGOD_DEFAULT);
3523 }
3524 
ui_name_vehicle(class Vehicle * vehicle)3525 void ui_name_vehicle(class Vehicle *vehicle)
3526 {
3527         int yesno;
3528         char buf[64];
3529 
3530         log_begin("Do you want to name your ");
3531         vehicle->describe();
3532         log_end("?");
3533 
3534         cmdwin_spush("Name");
3535         cmdwin_spush("<y/n>");
3536         getkey(&yesno, yesnokey);
3537         cmdwin_pop();
3538         cmdwin_pop();
3539 
3540         if (yesno == 'n') {
3541                 cmdwin_spush("no!");
3542                 log_msg("It's likely to be stolen!");
3543                 return;
3544         }
3545 
3546         if (!ui_getline(buf, sizeof(buf))) {
3547                 log_msg("It's likely to be stolen!");
3548                 return;
3549         }
3550 
3551         vehicle->setName(buf);
3552 
3553         log_begin("You christen ");
3554         vehicle->describe();
3555         log_end(".");
3556 }
3557 
cmdSettings(void)3558 void cmdSettings(void)
3559 {
3560         StatusMode omode = statusGetMode();
3561         options_menu();
3562         statusSetMode(omode);
3563 }
3564 
cmdDrop(class Character * actor)3565 void cmdDrop(class Character *actor)
3566 {
3567         enum StatusMode omode;
3568         struct inv_entry *ie;
3569         class Object *obj;
3570         int maxq, quantity, dir, x, y;
3571 
3572         assert(actor);
3573 
3574         cmdwin_clear();
3575         cmdwin_spush("Drop");
3576 
3577         omode = statusGetMode();
3578         statusBrowseContainer(actor->getInventoryContainer(), "Drop");
3579         ie = ui_select_item();
3580         statusSetMode(omode);
3581 
3582         if (!ie) {
3583                 return;
3584         }
3585 
3586         maxq = ie->count - ie->ref;
3587         assert(maxq);
3588 
3589         /* Don't drop quest items in temporary places! */
3590         if (place_is_wilderness_combat(actor->getPlace())
3591             && ie->type->isQuestItem()) {
3592                 log_msg("%s seems important, it might get lost here!",
3593                         ie->type->getName());
3594                 return;
3595         }
3596 
3597         /* prompt for a count (unless there is only one) */
3598         if (ie->count == 1) {
3599                 quantity = 1;
3600         } else {
3601                 quantity = ui_get_quantity(maxq);
3602                 if (!quantity) {
3603                         return;
3604                 }
3605         }
3606 
3607         /* prompt for location */
3608         dir = ui_get_direction();
3609 	if (dir == CANCEL) {
3610 		return;
3611         }
3612         x = actor->getX() + directionToDx(dir);
3613         y = actor->getY() + directionToDy(dir);
3614 
3615         /* put it on the map */
3616         obj = ie->type->createInstance();
3617         assert(obj);
3618         obj->setCount(quantity);
3619         if (place_get_movement_cost(actor->getPlace(), x, y,
3620                             obj, 0) < PTABLE_NO_DROP)
3621         {
3622 	        obj->relocate(actor->getPlace(), x, y,
3623    	                   REL_NOSTEP, /* FIXME: really? */
3624       	                NULL);
3625 	        actor->takeOut(ie->type, quantity);
3626 	        actor->runHook(OBJ_HOOK_DROP_DONE, "pd", ie->type, quantity);
3627    	  }
3628    	  else if (place_get_movement_cost(actor->getPlace(), actor->getX(), actor->getY(),
3629                             obj, 0) < PTABLE_IMPASSABLE)
3630         {
3631    	  	        obj->relocate(actor->getPlace(), actor->getX(), actor->getY(),
3632    	                   REL_NOSTEP, /* FIXME: really? */
3633       	                NULL);
3634 	        actor->takeOut(ie->type, quantity);
3635 	        actor->runHook(OBJ_HOOK_DROP_DONE, "pd", ie->type, quantity);
3636 	        log_msg("%s wouldnt fit!", ie->type->getName());
3637 		 }
3638 		 else
3639 		 {
3640 			 log_msg("Couldnt drop %s!", ie->type->getName());
3641 		 }
3642         /* remove from party inventory */
3643         actor->decActionPoints(kern_intvar_get("AP_COST:drop_item"));
3644 
3645         statusRepaint();
3646         mapUpdate(REPAINT_IF_DIRTY);
3647         return;
3648 }
3649 
3650 #ifdef USE_SKILLS
3651 
cmd_select_generic()3652 static const void *cmd_select_generic()
3653 {
3654 	struct KeyHandler kh;
3655 	struct ScrollerContext sc;
3656 
3657         foogodSetHintText(SCROLLER_HINT);
3658         foogodSetMode(FOOGOD_HINT);
3659 
3660 	sc.selector = SelectSuperGeneric;
3661 	sc.selection = NULL;
3662 	kh.fx = scroller;
3663 	kh.data = &sc;
3664 
3665 	eventPushKeyHandler(&kh);
3666 	cmdwin_push("<select>");
3667 	eventHandle();
3668 	cmdwin_pop();
3669 	eventPopKeyHandler();
3670 
3671         foogodSetMode(FOOGOD_DEFAULT);
3672         return sc.selection;
3673 }
3674 
3675 
3676 /* Do common front-end processing. Migrate all commands to start using this. */
cmd_front_end(class Character * pc,const char * cmdstr)3677 static class Character *cmd_front_end(class Character *pc, const char *cmdstr)
3678 {
3679         cmdwin_clear();
3680         cmdwin_spush(cmdstr);
3681 
3682         /* prompt user? */
3683         if (!pc) {
3684 
3685                 /* only one choice? */
3686                 if (player_party->get_num_living_members() == 1) {
3687                         pc = player_party->get_first_living_member();
3688                         cmdwin_spush(pc->getName());
3689                 } else {
3690                         pc = select_party_member();
3691                 }
3692 
3693         } else {
3694                 cmdwin_spush(pc->getName());
3695         }
3696 
3697         /* user abort? */
3698         if (!pc) {
3699                 return 0;
3700         }
3701 
3702         /* dead actor? */
3703         if (pc->isDead()) {
3704                 log_msg("%s - %s is too dead!", cmdstr, pc->getName());
3705                 cmdwin_push("can't!");
3706                 return 0;
3707         }
3708 
3709         /* sleeping actor? */
3710         if (pc->isAsleep()) {
3711                 log_msg("%s - %s rolls over and snores!", cmdstr,
3712                         pc->getName());
3713                 cmdwin_push("can't!");
3714                 return 0;
3715         }
3716 
3717         /* tell status who the actor is (sometimes it matters) */
3718         statusSelectCharacter(pc->getOrder());
3719 
3720         return pc;
3721 }
3722 
cmd_add_skill_set(struct node * head,class Character * pc,struct skill_set * skset)3723 static void cmd_add_skill_set(struct node *head, class Character *pc,
3724                               struct skill_set *skset)
3725 {
3726         struct list *elem;
3727         int pclvl = pc->getLevel();
3728 
3729         /* for each skill in the skill set */
3730         list_for_each(&skset->skills, elem) {
3731 
3732                 struct skill_set_entry *ssent;
3733                 struct node *node;
3734                 ssent = list_entry(elem, struct skill_set_entry, list);
3735 
3736                 /* is it a passive skill? */
3737                 if (ssent->skill->passive) {
3738                         continue;
3739                 }
3740 
3741                 /* is the character is of sufficient level? */
3742                 if (pclvl < ssent->level) {
3743                         continue;
3744                 }
3745 
3746                 /* add it to the list */
3747                 node = node_new(ssent);
3748                 node_add_tail(head, node);
3749         }
3750 }
3751 
cmd_build_skill_list(struct node * head,class Character * pc)3752 static void cmd_build_skill_list(struct node *head, class Character *pc)
3753 {
3754         node_init(head);
3755 
3756         /* add species skills */
3757         if (pc->species
3758             && pc->species->skills) {
3759                 cmd_add_skill_set(head, pc, pc->species->skills);
3760         }
3761 
3762         /* add occupation skills */
3763         if (pc->occ
3764             && pc->occ->skills) {
3765                 cmd_add_skill_set(head, pc, pc->occ->skills);
3766         }
3767 
3768         /* add bonus skills? */
3769 }
3770 
cmd_paint_skill(struct stat_super_generic_data * self,struct node * node,SDL_Rect * rect)3771 static int cmd_paint_skill(struct stat_super_generic_data *self,
3772                             struct node *node,
3773                             SDL_Rect *rect)
3774 {
3775         struct skill_set_entry *ssent = (struct skill_set_entry *)node->ptr;
3776         struct skill *skill = ssent->skill;
3777         const char *requires = "Requires:";
3778         struct node *tnode;
3779         struct list *elem;
3780         SDL_Rect orect;
3781         int complete = 0;
3782 
3783         /* remember original rect */
3784         orect = *rect;
3785 
3786         /* name */
3787         if (rect->h < ASCII_H) {
3788                 return -1;
3789         }
3790         screenPrint(rect, 0, "^c+m%s^c-", skill->name);
3791 
3792         /* level, ap, mp */
3793         screenPrint(rect, SP_RIGHTJUSTIFIED,
3794                     "^c+GLvl:^c+y%d^c- MP:^c+b%d^c- AP:^c+r%d^c-^c-",
3795                     ssent->level,
3796                     skill->mp,
3797                     skill->ap);
3798         rect->y += ASCII_H;
3799         rect->h -= ASCII_H;
3800 
3801         /* check for required items */
3802         if (! node_list_empty(&skill->tools)
3803             || ! list_empty(&skill->materials)) {
3804 
3805                 if (rect->h < ASCII_H) {
3806                         complete = -1;
3807                 } else {
3808 
3809                         /* print "requires:" */
3810                         screenPrint(rect, 0, " ^c+G%s^c-", requires);
3811 
3812                         /* temporarily change x to print to right of "requires" */
3813                         rect->x += (strlen(requires) + 1) * ASCII_W;
3814 
3815                         /* list tools */
3816                         node_for_each(&skill->tools, tnode) {
3817 
3818                                 if (rect->h < ASCII_H) {
3819                                         complete = -1;
3820                                         break;
3821                                 }
3822 
3823                                 class ObjectType *tool = (class ObjectType*)tnode->ptr;
3824                                 struct inv_entry *ie=player_party->inventory->
3825                                         search(tool);
3826                                 char tool_clr=(ie&&ie->count)?'g':'r';
3827                                 screenPrint(rect, 0, "^c+%c%s^c-", tool_clr,
3828                                             tool->getName());
3829 
3830                                 rect->y += ASCII_H;
3831                                 rect->h -= ASCII_H;
3832                         }
3833 
3834                         /* list materials */
3835                         list_for_each(&skill->materials, elem) {
3836 
3837                                 if (rect->h < ASCII_H) {
3838                                         complete = -1;
3839                                         break;
3840                                 }
3841 
3842                                 struct skill_material *mat =
3843                                         list_entry(elem, struct skill_material, list);
3844                                 class ObjectType *objtype =
3845                                         (class ObjectType*)mat->objtype;
3846                                 struct inv_entry *ie=player_party->inventory->
3847                                         search(objtype);
3848                                 char mat_clr=ie?'g':'r';
3849                                 char q_clr=(ie&&(ie->count>=mat->quantity))?'g':'r';
3850                                 screenPrint(rect, 0, "^c+%c%s^c+%c (%d/%d)^c-^c-",
3851                                             mat_clr,
3852                                             objtype->getName(),
3853                                             q_clr,
3854                                             mat->quantity,
3855                                             ie?ie->count:0);
3856 
3857                                 rect->y += ASCII_H;
3858                                 rect->h -= ASCII_H;
3859                         }
3860 
3861                         /* restore rect x */
3862                         rect->x = orect.x;
3863                 }
3864         }
3865 
3866         /* figure out how much area we used */
3867         orect.h = rect->y - orect.y;
3868 
3869         /* if this is not the currently selected item then shade it */
3870         if (self->selected != node) {
3871                 screenShade(&orect, 128);
3872         }
3873 
3874         return complete;
3875 }
3876 
cmd_skill_list_unref(struct stat_super_generic_data * self)3877 static void cmd_skill_list_unref(struct stat_super_generic_data *self)
3878 {
3879         struct node *node;
3880 
3881         /* Decrement the refcount. */
3882         assert(self->refcount > 0);
3883         self->refcount--;
3884         if (self->refcount > 0) {
3885                 return;
3886         }
3887 
3888         /* Cleanup if no more refs. */
3889         node = node_next(&self->list);
3890         while (node != &self->list) {
3891                 struct node *tmp = node;
3892                 node = node_next(node);
3893                 node_unref(tmp);
3894         }
3895 }
3896 
cmd_select_skill(class Character * pc)3897 static struct skill_set_entry *cmd_select_skill(class Character *pc)
3898 {
3899         struct skill_set_entry *ssent;
3900         struct node *selected;
3901         struct stat_super_generic_data data;
3902 
3903         /* setup the status browser data */
3904         memset(&data, 0, sizeof(data));
3905         cmd_build_skill_list(&data.list, pc);
3906         data.title = "Yuse";
3907         data.paint = cmd_paint_skill;
3908         data.unref = cmd_skill_list_unref;
3909 
3910         /* put the status browser in selection mode */
3911         statusSetSuperGenericData(&data);
3912         statusPushMode(SuperGeneric);
3913 
3914         /* wait for user selection */
3915         selected = (struct node*)cmd_select_generic();
3916 
3917         /* extract result */
3918         if (selected) {
3919                 ssent = (struct skill_set_entry *)selected->ptr;
3920                 cmdwin_push(ssent->skill->name);
3921         } else {
3922                 ssent = 0;
3923                 cmdwin_push("none");
3924         }
3925 
3926         /* restore browser status mode */
3927         statusPopMode();
3928 
3929         assert(! data.refcount);
3930 
3931         return ssent;
3932 }
3933 
cmdYuse(class Character * actor)3934 void cmdYuse(class Character *actor)
3935 {
3936         struct skill_set_entry *ssent;
3937         struct skill *skill;
3938         int cant = 0, result = 0, yused = 0;
3939 
3940         /* select/verify the actor */
3941         if (!(actor = cmd_front_end(actor, "Yuse"))) {
3942                 return;
3943         }
3944 
3945         /* select the skill to yuse */
3946         if (!(ssent = cmd_select_skill(actor))) {
3947                 return;
3948         }
3949         skill = ssent->skill;
3950         log_begin("%s: %s - ", actor->getName(), skill->name);
3951 
3952         /* check wilderness */
3953         if (! skill->wilderness_ok
3954             && place_is_wilderness(actor->getPlace())) {
3955                 cant = 1;
3956                 log_msg("Not in the wilderness!");
3957         }
3958 
3959         /* check level */
3960         if (actor->getLevel() < ssent->level) {
3961                 cant = 1;
3962                 log_msg("Must be level %d!", ssent->level);
3963         }
3964 
3965         /* check mana */
3966         if (actor->getMana() < skill->mp) {
3967                 cant = 1;
3968                 log_msg("Not enough mana!");
3969         }
3970 
3971         /* check tools */
3972         if (!node_list_empty(&skill->tools)) {
3973                 struct node *tnode;
3974                 node_for_each(&skill->tools, tnode) {
3975                         class ObjectType *tool = (class ObjectType*)tnode->ptr;
3976                         struct inv_entry *ie=player_party->inventory->
3977                                 search(tool);
3978                         if (!ie || !ie->count) {
3979                                 log_msg("Need %s!", tool->getName());
3980                                 cant = 1;
3981                         }
3982                 }
3983 
3984         }
3985 
3986         /* check material */
3987         if (! list_empty(&skill->materials)) {
3988                 struct list *elem;
3989                 struct skill_material *mat;
3990                 class ObjectType *objtype;
3991                 struct inv_entry *ie;
3992                 list_for_each(&skill->materials, elem) {
3993                         mat = list_entry(elem, struct skill_material, list);
3994                         objtype = (class ObjectType*)mat->objtype;
3995                         ie = player_party->inventory->search(objtype);
3996                         if (!ie || ie->count < mat->quantity) {
3997                                 cant = 1;
3998                                 log_msg("Need %d %s!", mat->quantity,
3999                                         objtype->getName());
4000                         }
4001                 }
4002         }
4003 
4004         /* check special */
4005         if (skill->can_yuse
4006             && ! closure_exec(skill->can_yuse, "p", actor)) {
4007                 cant = 1;
4008         }
4009 
4010         /* cant? */
4011         if (cant) {
4012                 cmdwin_push("failed!");
4013                 log_end("^c+rFailed!^c-");
4014                 return;
4015         }
4016 
4017         /* yuse the skill */
4018         result = closure_exec(skill->yuse, "p", actor);
4019         yused = cmd_eval_and_log_result(result);
4020 
4021         /* change ap/mp/xp and consume materials */
4022         if (yused) {
4023                 actor->runHook(OBJ_HOOK_YUSE_DONE, 0);
4024                 actor->addMana(0 - skill->mp);
4025                 actor->decActionPoints(skill->ap);
4026                 actor->addExperience(ssent->level);
4027 
4028                 if (! list_empty(&skill->materials)) {
4029                         struct list *elem;
4030                         struct skill_material *mat;
4031                         list_for_each(&skill->materials, elem) {
4032                                 mat = list_entry(elem, struct skill_material,
4033                                                  list);
4034                                 player_party->takeOut((class ObjectType*)
4035                                                       mat->objtype,
4036                                                       mat->quantity);
4037                         }
4038                 }
4039         }
4040 
4041         log_end(0);
4042 }
4043 #endif /* USE_SKILLS */
4044 
cmdSetSoloMode(int party_member_index)4045 bool cmdSetSoloMode(int party_member_index)
4046 {
4047     class Character *solo_member = player_party->getMemberAtIndex(party_member_index);
4048     if (solo_member
4049         && ! solo_member->isIncapacitated()
4050         && solo_member->isOnMap()
4051         && solo_member->isPlayerControlled()
4052         ) {
4053 
4054         if (solo_member->engagedInTask()) {
4055             log_msg("%s is engaged in %s, abort?", solo_member->getName(), solo_member->getTaskName());
4056             if (! ui_get_yes_no(solo_member->getName())) {
4057                 return false;
4058             }
4059         }
4060 
4061         player_party->enableSoloMode(solo_member);
4062         return true;
4063     }
4064     return false;
4065 }
4066 
cmdToggleFollowMode(void)4067 bool cmdToggleFollowMode(void)
4068 {
4069     log_begin("Follow mode ");
4070     if (player_party->getPartyControlMode() == PARTY_CONTROL_FOLLOW) {
4071         log_end("OFF");
4072         player_party->enableRoundRobinMode();
4073         return false;
4074     } else {
4075         log_end("ON");
4076         player_party->enableFollowMode();
4077         return true;
4078     }
4079 }
4080 
4081