OSDN Git Service

Merge pull request #3569 from sikabane-works/release/3.0.0.88-alpha
[hengbandforosx/hengbandosx.git] / src / autopick / autopick-editor-command.cpp
1 /*!
2  * @brief 自動拾いエディタ画面でキーを押した時の挙動一式
3  * @date 2020/04/26
4  * @author Hourier
5  * @todo これ単体で700行を超えているので要分割
6  */
7
8 #include "autopick/autopick-editor-command.h"
9 #include "autopick/autopick-commands-table.h"
10 #include "autopick/autopick-dirty-flags.h"
11 #include "autopick/autopick-drawer.h"
12 #include "autopick/autopick-editor-util.h"
13 #include "autopick/autopick-entry.h"
14 #include "autopick/autopick-finder.h"
15 #include "autopick/autopick-flags-table.h"
16 #include "autopick/autopick-inserter-killer.h"
17 #include "autopick/autopick-methods-table.h"
18 #include "autopick/autopick-reader-writer.h"
19 #include "autopick/autopick-util.h"
20 #include "core/asking-player.h"
21 #include "core/show-file.h"
22 #include "game-option/input-options.h"
23 #include "game-option/keymap-directory-getter.h"
24 #include "player-info/class-info.h"
25 #include "player-info/race-info.h"
26 #include "system/player-type-definition.h"
27 #include "term/term-color-types.h"
28 #include "term/z-form.h"
29 #include <string>
30 #include <string_view>
31
32 /*!
33  * @brief
34  * @param player_ptr プレイヤーへの参照ポインタ
35  * @param tb 自動拾いの構文
36  * @param com_id エディタ内で打ったコマンド
37  * @return
38  * @details Execute a single editor command
39  */
40 ape_quittance do_editor_command(PlayerType *player_ptr, text_body_type *tb, int com_id)
41 {
42     switch (com_id) {
43     case EC_QUIT: {
44         if (tb->changed) {
45             if (!input_check(_("全ての変更を破棄してから終了します。よろしいですか? ", "Discard all changes and quit. Are you sure? "))) {
46                 break;
47             }
48         }
49
50         return APE_QUIT_WITHOUT_SAVE;
51     }
52     case EC_SAVEQUIT:
53         return APE_QUIT_AND_SAVE;
54     case EC_REVERT: {
55         if (!input_check(_("全ての変更を破棄して元の状態に戻します。よろしいですか? ", "Discard all changes and revert to original file. Are you sure? "))) {
56             break;
57         }
58
59         free_text_lines(tb->lines_list);
60         tb->lines_list = read_pickpref_text_lines(player_ptr, &tb->filename_mode);
61         tb->dirty_flags |= DIRTY_ALL | DIRTY_MODE | DIRTY_EXPRESSION;
62         tb->cx = tb->cy = 0;
63         tb->mark = 0;
64         tb->changed = false;
65         break;
66     }
67     case EC_HELP: {
68         (void)show_file(player_ptr, true, _("jeditor.txt", "editor.txt"), 0, 0);
69         tb->dirty_flags |= DIRTY_SCREEN;
70         break;
71     }
72     case EC_RETURN: {
73         if (tb->mark) {
74             tb->mark = 0;
75             tb->dirty_flags |= DIRTY_ALL;
76         }
77
78         if (!insert_return_code(tb)) {
79             break;
80         }
81         tb->cy++;
82         tb->cx = 0;
83         tb->dirty_flags |= DIRTY_ALL;
84         break;
85     }
86     case EC_LEFT: {
87         if (0 < tb->cx) {
88             int len;
89 #ifdef JP
90             int i;
91 #endif
92             tb->cx--;
93             len = strlen(tb->lines_list[tb->cy]);
94             if (len < tb->cx) {
95                 tb->cx = len;
96             }
97 #ifdef JP
98             for (i = 0; tb->lines_list[tb->cy][i]; i++) {
99                 if (iskanji(tb->lines_list[tb->cy][i])) {
100                     i++;
101                     if (i == tb->cx) {
102                         tb->cx--;
103                         break;
104                     }
105                 }
106             }
107 #endif
108         } else if (tb->cy > 0) {
109             tb->cy--;
110             tb->cx = strlen(tb->lines_list[tb->cy]);
111         }
112
113         break;
114     }
115     case EC_DOWN: {
116         if (!tb->lines_list[tb->cy + 1]) {
117             if (!add_empty_line(tb)) {
118                 break;
119             }
120         }
121
122         tb->cy++;
123         break;
124     }
125     case EC_UP:
126         if (tb->cy > 0) {
127             tb->cy--;
128         }
129         break;
130     case EC_RIGHT: {
131         const int len = strlen(tb->lines_list[tb->cy]);
132 #ifdef JP
133         if ((tb->cx + 1 < len) && iskanji(tb->lines_list[tb->cy][tb->cx])) {
134             tb->cx++;
135         }
136 #endif
137         tb->cx++;
138
139         if (len < tb->cx) {
140             tb->cx = len;
141             if (!tb->lines_list[tb->cy + 1]) {
142                 if (!add_empty_line(tb)) {
143                     break;
144                 }
145             }
146
147             tb->cy++;
148             tb->cx = 0;
149         }
150
151         break;
152     }
153     case EC_BOL:
154         tb->cx = 0;
155         break;
156     case EC_EOL:
157         tb->cx = strlen(tb->lines_list[tb->cy]);
158         break;
159     case EC_PGUP:
160         while (0 < tb->cy && tb->upper <= tb->cy) {
161             tb->cy--;
162         }
163
164         while (0 < tb->upper && tb->cy + 1 < tb->upper + tb->hgt) {
165             tb->upper--;
166         }
167
168         break;
169     case EC_PGDOWN:
170         while (tb->cy < tb->upper + tb->hgt) {
171             if (!tb->lines_list[tb->cy + 1]) {
172                 if (!add_empty_line(tb)) {
173                     break;
174                 }
175             }
176
177             tb->cy++;
178         }
179
180         tb->upper = tb->cy;
181         break;
182     case EC_TOP:
183         tb->cy = 0;
184         break;
185     case EC_BOTTOM:
186         while (true) {
187             if (!tb->lines_list[tb->cy + 1]) {
188                 if (!add_empty_line(tb)) {
189                     break;
190                 }
191             }
192
193             tb->cy++;
194         }
195
196         tb->cx = 0;
197         break;
198     case EC_CUT: {
199         copy_text_to_yank(tb);
200         if (tb->my == tb->cy) {
201             int bx1 = std::min(tb->mx, tb->cx);
202             int bx2 = std::max(tb->mx, tb->cx);
203             int len = strlen(tb->lines_list[tb->cy]);
204             if (bx2 > len) {
205                 bx2 = len;
206             }
207
208             kill_line_segment(tb, tb->cy, bx1, bx2, true);
209             tb->cx = bx1;
210         } else {
211             int by1 = std::min(tb->my, tb->cy);
212             int by2 = std::max(tb->my, tb->cy);
213
214             for (int y = by2; y >= by1; y--) {
215                 int len = strlen(tb->lines_list[y]);
216
217                 kill_line_segment(tb, y, 0, len, true);
218             }
219
220             tb->cy = by1;
221             tb->cx = 0;
222         }
223
224         tb->mark = 0;
225         tb->dirty_flags |= DIRTY_ALL;
226         tb->changed = true;
227         break;
228     }
229     case EC_COPY: {
230         copy_text_to_yank(tb);
231
232         /*
233          * Move cursor position to the end of the selection
234          *
235          * Pressing ^C ^V correctly duplicates the selection.
236          */
237         if (tb->my != tb->cy) {
238             tb->cy = std::max(tb->cy, tb->my);
239             if (!tb->lines_list[tb->cy + 1]) {
240                 if (!add_empty_line(tb)) {
241                     break;
242                 }
243             }
244
245             tb->cy++;
246             break;
247         }
248
249         tb->cx = std::max(tb->cx, tb->mx);
250         if (!tb->lines_list[tb->cy][tb->cx]) {
251             if (!tb->lines_list[tb->cy + 1]) {
252                 if (!add_empty_line(tb)) {
253                     break;
254                 }
255             }
256
257             tb->cy++;
258             tb->cx = 0;
259         }
260
261         break;
262     }
263     case EC_PASTE: {
264         int len = strlen(tb->lines_list[tb->cy]);
265         if (tb->yank.empty()) {
266             break;
267         }
268         if (tb->cx > len) {
269             tb->cx = len;
270         }
271
272         if (tb->mark) {
273             tb->mark = 0;
274             tb->dirty_flags |= DIRTY_ALL;
275         }
276
277         for (auto i = 0U; i < tb->yank.size(); ++i) {
278             const std::string_view line(tb->lines_list[tb->cy]);
279             std::string buf(line.substr(0, tb->cx));
280             buf.append(tb->yank[i], 0, MAX_LINELEN - buf.length() - 1);
281
282             const auto is_last_line = i + 1 == tb->yank.size();
283             if (!is_last_line || tb->yank_eol) {
284                 if (!insert_return_code(tb)) {
285                     break;
286                 }
287                 string_free(tb->lines_list[tb->cy]);
288                 tb->lines_list[tb->cy] = string_make(buf.data());
289                 tb->cx = 0;
290                 tb->cy++;
291
292                 continue;
293             }
294
295             const auto rest = line.substr(tb->cx);
296             tb->cx = buf.length();
297             buf.append(rest, 0, MAX_LINELEN - buf.length() - 1);
298
299             string_free(tb->lines_list[tb->cy]);
300             tb->lines_list[tb->cy] = string_make(buf.data());
301             break;
302         }
303
304         tb->dirty_flags |= DIRTY_ALL;
305         tb->dirty_flags |= DIRTY_EXPRESSION;
306         tb->changed = true;
307         break;
308     }
309     case EC_BLOCK: {
310         if (tb->mark) {
311             tb->mark = 0;
312             tb->dirty_flags |= DIRTY_ALL;
313             break;
314         }
315
316         tb->mark = MARK_MARK;
317         if (com_id == tb->old_com_id) {
318             int tmp = tb->cy;
319             tb->cy = tb->my;
320             tb->my = tmp;
321             tmp = tb->cx;
322             tb->cx = tb->mx;
323             tb->mx = tmp;
324             tb->dirty_flags |= DIRTY_ALL;
325             break;
326         }
327
328         int len = strlen(tb->lines_list[tb->cy]);
329
330         tb->my = tb->cy;
331         tb->mx = tb->cx;
332         if (tb->cx > len) {
333             tb->mx = len;
334         }
335         break;
336     }
337     case EC_KILL_LINE: {
338         int len = strlen(tb->lines_list[tb->cy]);
339         if (tb->cx > len) {
340             tb->cx = len;
341         }
342
343         if (tb->mark) {
344             tb->mark = 0;
345             tb->dirty_flags |= DIRTY_ALL;
346         }
347
348         if (tb->old_com_id != com_id) {
349             kill_yank_chain(tb);
350         }
351
352         if (tb->cx < len) {
353             add_str_to_yank(tb, &(tb->lines_list[tb->cy][tb->cx]));
354             kill_line_segment(tb, tb->cy, tb->cx, len, false);
355             tb->dirty_line = tb->cy;
356             break;
357         }
358
359         if (tb->yank_eol) {
360             add_str_to_yank(tb, "");
361         }
362
363         tb->yank_eol = true;
364         do_editor_command(player_ptr, tb, EC_DELETE_CHAR);
365         break;
366     }
367     case EC_DELETE_CHAR: {
368         if (tb->mark) {
369             tb->mark = 0;
370             tb->dirty_flags |= DIRTY_ALL;
371         }
372
373 #ifdef JP
374         if (iskanji(tb->lines_list[tb->cy][tb->cx])) {
375             tb->cx++;
376         }
377 #endif
378         tb->cx++;
379         int len = strlen(tb->lines_list[tb->cy]);
380         if (len >= tb->cx) {
381             do_editor_command(player_ptr, tb, EC_BACKSPACE);
382             break;
383         }
384
385         if (tb->lines_list[tb->cy + 1]) {
386             tb->cy++;
387             tb->cx = 0;
388         } else {
389             tb->cx = len;
390             break;
391         }
392
393         do_editor_command(player_ptr, tb, EC_BACKSPACE);
394         break;
395     }
396     case EC_BACKSPACE: {
397         int len, i, j, k;
398         char buf[MAX_LINELEN];
399         if (tb->mark) {
400             tb->mark = 0;
401             tb->dirty_flags |= DIRTY_ALL;
402         }
403
404         len = strlen(tb->lines_list[tb->cy]);
405         if (len < tb->cx) {
406             tb->cx = len;
407         }
408
409         if (tb->cx == 0) {
410             if (tb->cy == 0) {
411                 break;
412             }
413             tb->cx = strlen(tb->lines_list[tb->cy - 1]);
414             strcpy(buf, tb->lines_list[tb->cy - 1]);
415             strcat(buf, tb->lines_list[tb->cy]);
416             string_free(tb->lines_list[tb->cy - 1]);
417             string_free(tb->lines_list[tb->cy]);
418             tb->lines_list[tb->cy - 1] = string_make(buf);
419
420             for (i = tb->cy; tb->lines_list[i + 1]; i++) {
421                 tb->lines_list[i] = tb->lines_list[i + 1];
422             }
423
424             tb->lines_list[i] = nullptr;
425             tb->cy--;
426             tb->dirty_flags |= DIRTY_ALL;
427             tb->dirty_flags |= DIRTY_EXPRESSION;
428             tb->changed = true;
429             break;
430         }
431
432         for (i = j = k = 0; tb->lines_list[tb->cy][i] && i < tb->cx; i++) {
433             k = j;
434 #ifdef JP
435             if (iskanji(tb->lines_list[tb->cy][i])) {
436                 buf[j++] = tb->lines_list[tb->cy][i++];
437             }
438 #endif
439             buf[j++] = tb->lines_list[tb->cy][i];
440         }
441
442         while (j > k) {
443             tb->cx--;
444             j--;
445         }
446
447         for (; tb->lines_list[tb->cy][i]; i++) {
448             buf[j++] = tb->lines_list[tb->cy][i];
449         }
450
451         buf[j] = '\0';
452         string_free(tb->lines_list[tb->cy]);
453         tb->lines_list[tb->cy] = string_make(buf);
454         tb->dirty_line = tb->cy;
455         check_expression_line(tb, tb->cy);
456         tb->changed = true;
457         break;
458     }
459     case EC_SEARCH_STR: {
460         byte search_dir;
461         tb->dirty_flags |= DIRTY_SCREEN;
462         search_dir = get_string_for_search(player_ptr, &tb->search_o_ptr, &tb->search_str);
463
464         if (!search_dir) {
465             break;
466         }
467
468         if (search_dir == 1) {
469             do_editor_command(player_ptr, tb, EC_SEARCH_FORW);
470         } else {
471             do_editor_command(player_ptr, tb, EC_SEARCH_BACK);
472         }
473
474         break;
475     }
476     case EC_SEARCH_FORW:
477         if (tb->search_o_ptr) {
478             search_for_object(player_ptr, tb, tb->search_o_ptr, true);
479             break;
480         }
481
482         if (tb->search_str && tb->search_str[0]) {
483             search_for_string(tb, tb->search_str, true);
484             break;
485         }
486
487         tb->dirty_flags |= DIRTY_NO_SEARCH;
488         break;
489
490     case EC_SEARCH_BACK: {
491         if (tb->search_o_ptr) {
492             search_for_object(player_ptr, tb, tb->search_o_ptr, false);
493             break;
494         }
495
496         if (tb->search_str && tb->search_str[0]) {
497             search_for_string(tb, tb->search_str, false);
498             break;
499         }
500
501         tb->dirty_flags |= DIRTY_NO_SEARCH;
502         break;
503     }
504     case EC_SEARCH_OBJ: {
505         tb->dirty_flags |= DIRTY_SCREEN;
506
507         if (!get_object_for_search(player_ptr, &tb->search_o_ptr, &tb->search_str)) {
508             break;
509         }
510
511         do_editor_command(player_ptr, tb, EC_SEARCH_FORW);
512         break;
513     }
514     case EC_SEARCH_DESTROYED: {
515         if (!get_destroyed_object_for_search(player_ptr, &tb->search_o_ptr, &tb->search_str)) {
516             tb->dirty_flags |= DIRTY_NO_SEARCH;
517             break;
518         }
519
520         do_editor_command(player_ptr, tb, EC_SEARCH_FORW);
521         break;
522     }
523     case EC_INSERT_OBJECT: {
524         autopick_type an_entry, *entry = &an_entry;
525         if (!entry_from_choosed_object(player_ptr, entry)) {
526             tb->dirty_flags |= DIRTY_SCREEN;
527             break;
528         }
529
530         tb->cx = 0;
531         if (!insert_return_code(tb)) {
532             break;
533         }
534         string_free(tb->lines_list[tb->cy]);
535         tb->lines_list[tb->cy] = autopick_line_from_entry(*entry);
536         tb->dirty_flags |= DIRTY_SCREEN;
537         break;
538     }
539     case EC_INSERT_DESTROYED: {
540         if (!tb->last_destroyed) {
541             break;
542         }
543
544         tb->cx = 0;
545         if (!insert_return_code(tb)) {
546             break;
547         }
548         string_free(tb->lines_list[tb->cy]);
549         tb->lines_list[tb->cy] = string_make(tb->last_destroyed);
550         tb->dirty_flags |= DIRTY_ALL;
551         tb->changed = true;
552         break;
553     }
554     case EC_INSERT_BLOCK: {
555         if (!can_insert_line(tb, 2)) {
556             break;
557         }
558         char expression[80];
559         strnfmt(expression, sizeof(expression), "?:[AND [EQU $RACE %s] [EQU $CLASS %s] [GEQ $LEVEL %02d]]",
560 #ifdef JP
561             rp_ptr->E_title, cp_ptr->E_title,
562 #else
563             rp_ptr->title, cp_ptr->title,
564 #endif
565             player_ptr->lev);
566         tb->cx = 0;
567         insert_return_code(tb);
568         string_free(tb->lines_list[tb->cy]);
569         tb->lines_list[tb->cy] = string_make(expression);
570         tb->cy++;
571         insert_return_code(tb);
572         string_free(tb->lines_list[tb->cy]);
573         tb->lines_list[tb->cy] = string_make("?:1");
574         tb->dirty_flags |= DIRTY_ALL;
575         tb->changed = true;
576         break;
577     }
578     case EC_INSERT_MACRO: {
579         draw_text_editor(player_ptr, tb);
580         term_erase(0, tb->cy - tb->upper + 1, tb->wid);
581         term_putstr(0, tb->cy - tb->upper + 1, tb->wid - 1, TERM_YELLOW, _("P:<トリガーキー>: ", "P:<Trigger key>: "));
582         if (!insert_macro_line(tb)) {
583             break;
584         }
585
586         tb->cx = 2;
587         tb->dirty_flags |= DIRTY_ALL;
588         tb->changed = true;
589         break;
590     }
591     case EC_INSERT_KEYMAP: {
592         draw_text_editor(player_ptr, tb);
593         term_erase(0, tb->cy - tb->upper + 1, tb->wid);
594         term_putstr(0, tb->cy - tb->upper + 1, tb->wid - 1, TERM_YELLOW,
595             format(_("C:%d:<コマンドキー>: ", "C:%d:<Keypress>: "), (rogue_like_commands ? KEYMAP_MODE_ROGUE : KEYMAP_MODE_ORIG)));
596
597         if (!insert_keymap_line(tb)) {
598             break;
599         }
600
601         tb->cx = 2;
602         tb->dirty_flags |= DIRTY_ALL;
603         tb->changed = true;
604         break;
605     }
606     case EC_CL_AUTOPICK:
607         toggle_command_letter(tb, DO_AUTOPICK);
608         break;
609     case EC_CL_DESTROY:
610         toggle_command_letter(tb, DO_AUTODESTROY);
611         break;
612     case EC_CL_LEAVE:
613         toggle_command_letter(tb, DONT_AUTOPICK);
614         break;
615     case EC_CL_QUERY:
616         toggle_command_letter(tb, DO_QUERY_AUTOPICK);
617         break;
618     case EC_CL_NO_DISP:
619         toggle_command_letter(tb, DO_DISPLAY);
620         break;
621     case EC_IK_UNAWARE:
622         toggle_keyword(tb, FLG_UNAWARE);
623         break;
624     case EC_IK_UNIDENTIFIED:
625         toggle_keyword(tb, FLG_UNIDENTIFIED);
626         break;
627     case EC_IK_IDENTIFIED:
628         toggle_keyword(tb, FLG_IDENTIFIED);
629         break;
630     case EC_IK_STAR_IDENTIFIED:
631         toggle_keyword(tb, FLG_STAR_IDENTIFIED);
632         break;
633     case EC_KK_WEAPONS:
634         toggle_keyword(tb, FLG_WEAPONS);
635         break;
636     case EC_KK_FAVORITE_WEAPONS:
637         toggle_keyword(tb, FLG_FAVORITE_WEAPONS);
638         break;
639     case EC_KK_ARMORS:
640         toggle_keyword(tb, FLG_ARMORS);
641         break;
642     case EC_KK_MISSILES:
643         toggle_keyword(tb, FLG_MISSILES);
644         break;
645     case EC_KK_DEVICES:
646         toggle_keyword(tb, FLG_DEVICES);
647         break;
648     case EC_KK_LIGHTS:
649         toggle_keyword(tb, FLG_LIGHTS);
650         break;
651     case EC_KK_JUNKS:
652         toggle_keyword(tb, FLG_JUNKS);
653         break;
654     case EC_KK_CORPSES:
655         toggle_keyword(tb, FLG_CORPSES);
656         break;
657     case EC_KK_SPELLBOOKS:
658         toggle_keyword(tb, FLG_SPELLBOOKS);
659         break;
660     case EC_KK_SHIELDS:
661         toggle_keyword(tb, FLG_SHIELDS);
662         break;
663     case EC_KK_BOWS:
664         toggle_keyword(tb, FLG_BOWS);
665         break;
666     case EC_KK_RINGS:
667         toggle_keyword(tb, FLG_RINGS);
668         break;
669     case EC_KK_AMULETS:
670         toggle_keyword(tb, FLG_AMULETS);
671         break;
672     case EC_KK_SUITS:
673         toggle_keyword(tb, FLG_SUITS);
674         break;
675     case EC_KK_CLOAKS:
676         toggle_keyword(tb, FLG_CLOAKS);
677         break;
678     case EC_KK_HELMS:
679         toggle_keyword(tb, FLG_HELMS);
680         break;
681     case EC_KK_GLOVES:
682         toggle_keyword(tb, FLG_GLOVES);
683         break;
684     case EC_KK_BOOTS:
685         toggle_keyword(tb, FLG_BOOTS);
686         break;
687     case EC_OK_COLLECTING:
688         toggle_keyword(tb, FLG_COLLECTING);
689         break;
690     case EC_OK_BOOSTED:
691         toggle_keyword(tb, FLG_BOOSTED);
692         break;
693     case EC_OK_MORE_DICE:
694         toggle_keyword(tb, FLG_MORE_DICE);
695         break;
696     case EC_OK_MORE_BONUS:
697         toggle_keyword(tb, FLG_MORE_BONUS);
698         break;
699     case EC_OK_WORTHLESS:
700         toggle_keyword(tb, FLG_WORTHLESS);
701         break;
702     case EC_OK_ARTIFACT:
703         toggle_keyword(tb, FLG_ARTIFACT);
704         break;
705     case EC_OK_EGO:
706         toggle_keyword(tb, FLG_EGO);
707         break;
708     case EC_OK_GOOD:
709         toggle_keyword(tb, FLG_GOOD);
710         break;
711     case EC_OK_NAMELESS:
712         toggle_keyword(tb, FLG_NAMELESS);
713         break;
714     case EC_OK_AVERAGE:
715         toggle_keyword(tb, FLG_AVERAGE);
716         break;
717     case EC_OK_RARE:
718         toggle_keyword(tb, FLG_RARE);
719         break;
720     case EC_OK_COMMON:
721         toggle_keyword(tb, FLG_COMMON);
722         break;
723     case EC_OK_WANTED:
724         toggle_keyword(tb, FLG_WANTED);
725         break;
726     case EC_OK_UNIQUE:
727         toggle_keyword(tb, FLG_UNIQUE);
728         break;
729     case EC_OK_HUMAN:
730         toggle_keyword(tb, FLG_HUMAN);
731         break;
732     case EC_OK_UNREADABLE:
733         toggle_keyword(tb, FLG_UNREADABLE);
734         add_keyword(tb, FLG_SPELLBOOKS);
735         break;
736     case EC_OK_REALM1:
737         toggle_keyword(tb, FLG_REALM1);
738         add_keyword(tb, FLG_SPELLBOOKS);
739         break;
740     case EC_OK_REALM2:
741         toggle_keyword(tb, FLG_REALM2);
742         add_keyword(tb, FLG_SPELLBOOKS);
743         break;
744     case EC_OK_FIRST:
745         toggle_keyword(tb, FLG_FIRST);
746         add_keyword(tb, FLG_SPELLBOOKS);
747         break;
748     case EC_OK_SECOND:
749         toggle_keyword(tb, FLG_SECOND);
750         add_keyword(tb, FLG_SPELLBOOKS);
751         break;
752     case EC_OK_THIRD:
753         toggle_keyword(tb, FLG_THIRD);
754         add_keyword(tb, FLG_SPELLBOOKS);
755         break;
756     case EC_OK_FOURTH:
757         toggle_keyword(tb, FLG_FOURTH);
758         add_keyword(tb, FLG_SPELLBOOKS);
759         break;
760     }
761
762     tb->old_com_id = com_id;
763     return APE_QUIT;
764 }