1 /*
2 * File: macro.cc
3 * Summary: Crude macro-capability
4 * Written by: Juho Snellman <jsnell@lyseo.edu.ouka.fi>
5 *
6 * Change History (most recent first):
7 *
8 * <3> 6/25/02 JS Completely rewritten
9 * <2> 6/13/99 BWR SysEnv.crawl_dir support
10 * <1> -/--/-- JS Created
11 */
12
13 /*
14 * The macro-implementation works like this:
15 * - For generic game code, #define getch() getchm().
16 * - getchm() works by reading characters from an internal
17 * buffer. If none are available, new characters are read into
18 * the buffer with getch_mul().
19 * - getch_mul() reads at least one character, but will read more
20 * if available (determined using kbhit(), which should be defined
21 * in the platform specific libraries).
22 * - Before adding the characters read into the buffer, any macros
23 * in the sequence are replaced (see macro_add_buf_long for the
24 * details).
25 *
26 * (When the above text mentions characters, it actually means int).
27 */
28
29 #include "AppHdr.h"
30
31 #ifdef USE_MACROS
32 #define MACRO_CC
33 #include "macro.h"
34
35 #include <iostream>
36 #include <fstream>
37 #include <string>
38 #include <map>
39 #include <deque>
40
41 #include <stdio.h> // for snprintf
42 #include <ctype.h> // for tolower
43
44 #include "externs.h"
45
46 // for trim_string:
47 #include "initfile.h"
48
49 typedef std::deque<int> keyseq;
50 typedef std::deque<int> keybuf;
51 typedef std::map<keyseq,keyseq> macromap;
52
53 static macromap Keymaps;
54 static macromap Macros;
55
56 static keybuf Buffer;
57
58 /*
59 * Returns the name of the file that contains macros.
60 */
get_macro_file()61 static std::string get_macro_file()
62 {
63 std::string s;
64
65 if (SysEnv.crawl_dir)
66 s = SysEnv.crawl_dir;
67
68 return (s + "macro.txt");
69 }
70
71 /*
72 * Takes as argument a string, and returns a sequence of keys described
73 * by the string. Most characters produce their own ASCII code. There
74 * are two special cases:
75 * \\ produces the ASCII code of a single \
76 * \{123} produces 123 (decimal)
77 */
parse_keyseq(std::string s)78 static keyseq parse_keyseq( std::string s )
79 {
80 int state = 0;
81 keyseq v;
82 int num;
83
84 for (std::string::iterator i = s.begin(); i != s.end(); i++)
85 {
86 char c = *i;
87
88 switch (state)
89 {
90 case 0: // Normal state
91 if (c == '\\') {
92 state = 1;
93 } else {
94 v.push_back(c);
95 }
96 break;
97
98 case 1: // Last char is a '\'
99 if (c == '\\') {
100 state = 0;
101 v.push_back(c);
102 } else if (c == '{') {
103 state = 2;
104 num = 0;
105 }
106 // XXX Error handling
107 break;
108
109 case 2: // Inside \{}
110 if (c == '}') {
111 v.push_back(num);
112 state = 0;
113 } else if (c >= '0' && c <= '9') {
114 num = num * 10 + c - '0';
115 }
116 // XXX Error handling
117 break;
118 }
119 }
120
121 return (v);
122 }
123
124 /*
125 * Serializes a key sequence into a string of the format described
126 * above.
127 */
vtostr(keyseq v)128 static std::string vtostr( keyseq v )
129 {
130 std::string s;
131
132 for (keyseq::iterator i = v.begin(); i != v.end(); i++)
133 {
134 if (*i < 32 || *i > 127) {
135 char buff[10];
136
137 snprintf( buff, sizeof(buff), "\\{%d}", *i );
138 s += std::string( buff );
139
140 // Removing the stringstream code because its highly
141 // non-portable. For starters, people and compilers
142 // are supposed to be using the <sstream> implementation
143 // (with the ostringstream class) not the old <strstream>
144 // version, but <sstream> is not as available as it should be.
145 //
146 // The strstream implementation isn't very standard
147 // either: some compilers require the "ends" marker,
148 // others don't (and potentially fatal errors can
149 // happen if you don't have it correct for the system...
150 // ie its hard to make portable). It also isn't a very
151 // good implementation to begin with.
152 //
153 // Everyone should have snprintf()... we supply a version
154 // in libutil.cc to make sure of that! -- bwr
155 //
156 // std::ostrstream ss;
157 // ss << "\\{" << *i << "}" << ends;
158 // s += ss.str();
159 } else if (*i == '\\') {
160 s += "\\\\";
161 } else {
162 s += *i;
163 }
164 }
165
166 return (s);
167 }
168
169 /*
170 * Add a macro (suprise, suprise).
171 */
macro_add(macromap & mapref,keyseq key,keyseq action)172 static void macro_add( macromap &mapref, keyseq key, keyseq action )
173 {
174 mapref[key] = action;
175 }
176
177 /*
178 * Remove a macro.
179 */
macro_del(macromap & mapref,keyseq key)180 static void macro_del( macromap &mapref, keyseq key )
181 {
182 mapref.erase( key );
183 }
184
185
186 /*
187 * Adds keypresses from a sequence into the internal keybuffer. Ignores
188 * macros.
189 */
macro_buf_add(keyseq actions)190 static void macro_buf_add( keyseq actions )
191 {
192 for (keyseq::iterator i = actions.begin(); i != actions.end(); i++)
193 Buffer.push_back(*i);
194 }
195
196 /*
197 * Adds a single keypress into the internal keybuffer.
198 */
macro_buf_add(int key)199 static void macro_buf_add( int key )
200 {
201 Buffer.push_back( key );
202 }
203
204
205 /*
206 * Adds keypresses from a sequence into the internal keybuffer. Does some
207 * O(N^2) analysis to the sequence to replace macros.
208 */
macro_buf_add_long(keyseq actions)209 static void macro_buf_add_long( keyseq actions )
210 {
211 keyseq tmp;
212
213 // debug << "Adding: " << vtostr(actions) << endl;
214 // debug.flush();
215
216 // Check whether any subsequences of the sequence are macros.
217 // The matching starts from as early as possible, and is
218 // as long as possible given the first constraint. I.e from
219 // the sequence "abcdef" and macros "ab", "bcde" and "de"
220 // "ab" and "de" are recognized as macros.
221
222 while (actions.size() > 0)
223 {
224 tmp = actions;
225
226 while (tmp.size() > 0)
227 {
228 keyseq result = Keymaps[tmp];
229
230 // Found a macro. Add the expansion (action) of the
231 // macro into the buffer.
232 if (result.size() > 0) {
233 macro_buf_add( result );
234 break;
235 }
236
237 // Didn't find a macro. Remove a key from the end
238 // of the sequence, and try again.
239 tmp.pop_back();
240 }
241
242 if (tmp.size() == 0) {
243 // Didn't find a macro. Add the first keypress of the sequence
244 // into the buffer, remove it from the sequence, and try again.
245 macro_buf_add( actions.front() );
246 actions.pop_front();
247
248 } else {
249 // Found a macro, which has already been added above. Now just
250 // remove the macroed keys from the sequence.
251 for (unsigned int i = 0; i < tmp.size(); i++)
252 actions.pop_front();
253 }
254 }
255 }
256
257 /*
258 * Command macros are only applied from the immediate front of the
259 * buffer, and only when the game is expecting a command.
260 */
macro_buf_apply_command_macro(void)261 static void macro_buf_apply_command_macro( void )
262 {
263 keyseq tmp = Buffer;
264
265 // find the longest match from the start of the buffer and replace it
266 while (tmp.size() > 0)
267 {
268 keyseq result = Macros[tmp];
269
270 if (result.size() > 0)
271 {
272 // Found macro, remove match from front:
273 for (unsigned int i = 0; i < tmp.size(); i++)
274 Buffer.pop_front();
275
276 // Add macro to front:
277 for (keyseq::reverse_iterator k = result.rbegin(); k != result.rend(); k++)
278 Buffer.push_front(*k);
279
280 break;
281 }
282
283 tmp.pop_back();
284 }
285 }
286
287 /*
288 * Removes the earlies keypress from the keybuffer, and returns its
289 * value. If buffer was empty, returns -1;
290 */
macro_buf_get(void)291 static int macro_buf_get( void )
292 {
293 if (Buffer.size() == 0)
294 return (-1);
295
296 int key = Buffer.front();
297 Buffer.pop_front();
298
299 return (key);
300 }
301
302 /*
303 * Saves macros into the macrofile, overwriting the old one.
304 */
macro_save(void)305 void macro_save( void )
306 {
307 std::ofstream f;
308 f.open( get_macro_file().c_str() );
309
310 f << "# WARNING: This file is entirely auto-generated." << std::endl
311 << std::endl << "# Key Mappings:" << std::endl;
312
313 for (macromap::iterator i = Keymaps.begin(); i != Keymaps.end(); i++)
314 {
315 // Need this check, since empty values are added into the
316 // macro struct for all used keyboard commands.
317 if ((*i).second.size() > 0)
318 {
319 f << "K:" << vtostr((*i).first) << std::endl
320 << "A:" << vtostr((*i).second) << std::endl << std::endl;
321 }
322 }
323
324 f << "# Command Macros:" << std::endl;
325
326 for (macromap::iterator i = Macros.begin(); i != Macros.end(); i++)
327 {
328 // Need this check, since empty values are added into the
329 // macro struct for all used keyboard commands.
330 if ((*i).second.size() > 0)
331 {
332 f << "M:" << vtostr((*i).first) << std::endl
333 << "A:" << vtostr((*i).second) << std::endl << std::endl;
334 }
335 }
336
337 f.close();
338 }
339
340 /*
341 * Reads as many keypresses as are available (waiting for at least one),
342 * and returns them as a single keyseq.
343 */
getch_mul(void)344 static keyseq getch_mul( void )
345 {
346 keyseq keys;
347 int a;
348
349 keys.push_back( a = getch() );
350
351 // The a == 0 test is legacy code that I don't dare to remove. I
352 // have a vague recollection of it being a kludge for conio support.
353 while ((kbhit() || a == 0)) {
354 keys.push_back( a = getch() );
355 }
356
357 return (keys);
358 }
359
360 /*
361 * Replacement for getch(). Returns keys from the key buffer if available.
362 * If not, adds some content to the buffer, and returns some of it.
363 */
getchm(void)364 int getchm( void )
365 {
366 int a;
367
368 // Got data from buffer.
369 if ((a = macro_buf_get()) != -1)
370 return (a);
371
372 // Read some keys...
373 keyseq keys = getch_mul();
374 // ... and add them into the buffer
375 macro_buf_add_long( keys );
376
377 return (macro_buf_get());
378 }
379
380 /*
381 * Replacement for getch(). Returns keys from the key buffer if available.
382 * If not, adds some content to the buffer, and returns some of it.
383 */
getch_with_command_macros(void)384 int getch_with_command_macros( void )
385 {
386 if (Buffer.size() == 0)
387 {
388 // Read some keys...
389 keyseq keys = getch_mul();
390 // ... and add them into the buffer (apply keymaps)
391 macro_buf_add_long( keys );
392 }
393
394 // Apply longest matching macro at front of buffer:
395 macro_buf_apply_command_macro();
396
397 return (macro_buf_get());
398 }
399
400 /*
401 * Flush the buffer. Later we'll probably want to give the player options
402 * as to when this happens (ex. always before command input, casting failed).
403 */
flush_input_buffer(int reason)404 void flush_input_buffer( int reason )
405 {
406 if (Options.flush_input[ reason ])
407 Buffer.clear();
408 }
409
macro_add_query(void)410 void macro_add_query( void )
411 {
412 unsigned char input;
413 bool keymap = false;
414
415 mpr( "Command (m)acro or (k)eymap? ", MSGCH_PROMPT );
416 input = getch();
417 if (input == 0)
418 input = getch();
419
420 input = tolower( input );
421 if (input == 'k')
422 keymap = true;
423 else if (input == 'm')
424 keymap = false;
425 else
426 {
427 mpr( "Aborting." );
428 return;
429 }
430
431 // reference to the appropriate mapping
432 macromap &mapref = (keymap ? Keymaps : Macros);
433
434 snprintf( info, INFO_SIZE, "Input %s trigger key: ",
435 (keymap ? "keymap" : "macro") );
436
437 mpr( info, MSGCH_PROMPT );
438 keyseq key = getch_mul();
439
440 cprintf( "%s" EOL, (vtostr( key )).c_str() ); // echo key to screen
441
442 if (mapref[key].size() > 0)
443 {
444 snprintf( info, INFO_SIZE, "Current Action: %s",
445 (vtostr( mapref[key] )).c_str() );
446
447 mpr( info, MSGCH_WARN );
448 mpr( "Do you wish to (r)edefine, (c)lear, or (a)bort?", MSGCH_PROMPT );
449
450 input = getch();
451 if (input == 0)
452 input = getch();
453
454 input = tolower( input );
455 if (input == 'a' || input == ESCAPE)
456 {
457 mpr( "Aborting." );
458 return;
459 }
460 else if (input == 'c')
461 {
462 mpr( "Cleared." );
463 macro_del( mapref, key );
464 return;
465 }
466 }
467
468 mpr( "Input Macro Action: ", MSGCH_PROMPT );
469
470 // Using getch_mul() here isn't very useful... We'd like the
471 // flexibility to define multicharacter macros without having
472 // to resort to editing files and restarting the game. -- bwr
473 // keyseq act = getch_mul();
474
475 keyseq act;
476 char buff[4096];
477
478 get_input_line( buff, sizeof(buff) );
479
480 // convert c_str to keyseq
481 const int len = strlen( buff );
482 for (int i = 0; i < len; i++)
483 act.push_back( buff[i] );
484
485 macro_add( mapref, key, act );
486 }
487
488
489 /*
490 * Initializes the macros.
491 */
macro_init(void)492 int macro_init( void )
493 {
494 std::string s;
495 std::ifstream f;
496 keyseq key, action;
497 bool keymap = false;
498
499 f.open( get_macro_file().c_str() );
500
501 while (f >> s)
502 {
503 trim_string(s); // remove white space from ends
504
505 if (s[0] == '#') {
506 continue; // skip comments
507
508 } else if (s.substr(0, 2) == "K:") {
509 key = parse_keyseq(s.substr(2));
510 keymap = true;
511
512 } else if (s.substr(0, 2) == "M:") {
513 key = parse_keyseq(s.substr(2));
514 keymap = false;
515
516 } else if (s.substr(0, 2) == "A:") {
517 action = parse_keyseq(s.substr(2));
518 macro_add( (keymap ? Keymaps : Macros), key, action );
519 }
520 }
521
522 return (0);
523 }
524
525 #endif
526