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 = ≻
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 = ≻
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 = ≻
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 = ≻
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 = ≻
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 = ≻
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 = ≻
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