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