summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/macro.cc
diff options
context:
space:
mode:
authornlanza <nlanza@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-13 02:19:00 +0000
committernlanza <nlanza@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-13 02:19:00 +0000
commitaa88fdd8c6ad2da5eb5bd933e2d53d56cd8c176f (patch)
treed0551b96eaebb5b55694579fb8dae4abc7a38407 /crawl-ref/source/macro.cc
parent2b32f164e6ca1c4b3d587789f6cf46f46fe02fe8 (diff)
downloadcrawl-ref-aa88fdd8c6ad2da5eb5bd933e2d53d56cd8c176f.tar.gz
crawl-ref-aa88fdd8c6ad2da5eb5bd933e2d53d56cd8c176f.zip
Clean up a mistake in the SVN import.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@10 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source/macro.cc')
-rw-r--r--crawl-ref/source/macro.cc731
1 files changed, 731 insertions, 0 deletions
diff --git a/crawl-ref/source/macro.cc b/crawl-ref/source/macro.cc
new file mode 100644
index 0000000000..13838a947c
--- /dev/null
+++ b/crawl-ref/source/macro.cc
@@ -0,0 +1,731 @@
+/*
+ * File: macro.cc
+ * Summary: Crude macro-capability
+ * Written by: Juho Snellman <jsnell@lyseo.edu.ouka.fi>
+ *
+ * Change History (most recent first):
+ *
+ * <3> 6/25/02 JS Completely rewritten
+ * <2> 6/13/99 BWR SysEnv.crawl_dir support
+ * <1> -/--/-- JS Created
+ */
+
+/*
+ * The macro-implementation works like this:
+ * - For generic game code, #define getch() getchm().
+ * - getchm() works by reading characters from an internal
+ * buffer. If none are available, new characters are read into
+ * the buffer with getch_mul().
+ * - getch_mul() reads at least one character, but will read more
+ * if available (determined using kbhit(), which should be defined
+ * in the platform specific libraries).
+ * - Before adding the characters read into the buffer, any macros
+ * in the sequence are replaced (see macro_add_buf_long for the
+ * details).
+ *
+ * (When the above text mentions characters, it actually means int).
+ */
+
+#include "AppHdr.h"
+
+#ifdef USE_MACROS
+#define MACRO_CC
+#include "macro.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <map>
+#include <deque>
+#include <vector>
+
+#include <stdio.h> // for snprintf
+#include <ctype.h> // for tolower
+
+#include "externs.h"
+#include "stuff.h"
+
+// for trim_string:
+#include "initfile.h"
+
+typedef std::deque<int> keyseq;
+typedef std::deque<int> keybuf;
+typedef std::map<keyseq,keyseq> macromap;
+
+static macromap Keymaps[KC_CONTEXT_COUNT];
+static macromap Macros;
+
+static macromap *all_maps[] =
+{
+ &Keymaps[KC_DEFAULT],
+ &Keymaps[KC_LEVELMAP],
+ &Keymaps[KC_TARGETING],
+ &Macros,
+};
+
+static keybuf Buffer;
+
+#define USERFUNCBASE -10000
+static std::vector<std::string> userfunctions;
+
+inline int userfunc_index(int key)
+{
+ int index = (key <= USERFUNCBASE? USERFUNCBASE - key : -1);
+ return (index < 0 || index >= (int) userfunctions.size()? -1 : index);
+}
+
+static int userfunc_index(const keyseq &seq)
+{
+ if (seq.empty())
+ return (-1);
+
+ return userfunc_index(seq.front());
+}
+
+bool is_userfunction(int key)
+{
+ return (userfunc_index(key) != -1);
+}
+
+static bool is_userfunction(const keyseq &seq)
+{
+ return (userfunc_index(seq) != -1);
+}
+
+const char *get_userfunction(int key)
+{
+ int index = userfunc_index(key);
+ return (index == -1? NULL : userfunctions[index].c_str());
+}
+
+static const char *get_userfunction(const keyseq &seq)
+{
+ int index = userfunc_index(seq);
+ return (index == -1? NULL : userfunctions[index].c_str());
+}
+
+static bool userfunc_referenced(int index, const macromap &mm)
+{
+ for (macromap::const_iterator i = mm.begin(); i != mm.end(); i++)
+ {
+ if (userfunc_index(i->second) == index)
+ return (true);
+ }
+ return (false);
+}
+
+static bool userfunc_referenced(int index)
+{
+ for (unsigned i = 0; i < sizeof(all_maps) / sizeof(*all_maps); ++i)
+ {
+ macromap *m = all_maps[i];
+ if (userfunc_referenced(index, *m))
+ return (true);
+ }
+ return (false);
+}
+
+// Expensive function to discard unused function names
+static void userfunc_collectgarbage(void)
+{
+ for (int i = userfunctions.size() - 1; i >= 0; --i)
+ {
+ if (!userfunctions.empty() && !userfunc_referenced(i))
+ userfunctions[i].clear();
+ }
+}
+
+static int userfunc_getindex(const std::string &fname)
+{
+ if (fname.length() == 0)
+ return (-1);
+
+ userfunc_collectgarbage();
+
+ // Pass 1 to see if we have this string already
+ for (int i = 0, count = userfunctions.size(); i < count; ++i)
+ {
+ if (userfunctions[i] == fname)
+ return (i);
+ }
+
+ // Pass 2 to hunt for gaps
+ for (int i = 0, count = userfunctions.size(); i < count; ++i)
+ {
+ if (userfunctions[i].empty())
+ {
+ userfunctions[i] = fname;
+ return (i);
+ }
+ }
+
+ userfunctions.push_back(fname);
+ return (userfunctions.size() - 1);
+}
+
+/*
+ * Returns the name of the file that contains macros.
+ */
+static std::string get_macro_file()
+{
+ std::string s;
+
+ if (SysEnv.crawl_dir)
+ s = SysEnv.crawl_dir;
+
+ return (s + "macro.txt");
+}
+
+static void buf2keyseq(const char *buff, keyseq &k)
+{
+ // Sanity check
+ if (!buff)
+ return;
+
+ // convert c_str to keyseq
+
+ // Check for user functions
+ if (*buff == '=' && buff[1] == '=' && buff[2] == '=' && buff[3])
+ {
+ int index = userfunc_getindex(buff + 3);
+ if (index != -1)
+ k.push_back( USERFUNCBASE - index );
+ }
+ else
+ {
+ const int len = strlen( buff );
+ for (int i = 0; i < len; i++)
+ k.push_back( buff[i] );
+ }
+}
+
+/*
+ * Takes as argument a string, and returns a sequence of keys described
+ * by the string. Most characters produce their own ASCII code. There
+ * are two special cases:
+ * \\ produces the ASCII code of a single \
+ * \{123} produces 123 (decimal)
+ */
+static keyseq parse_keyseq( std::string s )
+{
+ int state = 0;
+ keyseq v;
+ int num;
+
+ if (s.find("===") == 0)
+ {
+ buf2keyseq(s.c_str(), v);
+ return (v);
+ }
+
+ for (std::string::iterator i = s.begin(); i != s.end(); i++)
+ {
+ char c = *i;
+
+ switch (state)
+ {
+ case 0: // Normal state
+ if (c == '\\') {
+ state = 1;
+ } else {
+ v.push_back(c);
+ }
+ break;
+
+ case 1: // Last char is a '\'
+ if (c == '\\') {
+ state = 0;
+ v.push_back(c);
+ } else if (c == '{') {
+ state = 2;
+ num = 0;
+ }
+ // XXX Error handling
+ break;
+
+ case 2: // Inside \{}
+ if (c == '}') {
+ v.push_back(num);
+ state = 0;
+ } else if (c >= '0' && c <= '9') {
+ num = num * 10 + c - '0';
+ }
+ // XXX Error handling
+ break;
+ }
+ }
+
+ return (v);
+}
+
+/*
+ * Serializes a key sequence into a string of the format described
+ * above.
+ */
+static std::string vtostr( const keyseq &seq )
+{
+ std::string s;
+
+ const keyseq *v = &seq;
+ keyseq dummy;
+ if (is_userfunction(seq))
+ {
+ // Laboriously reconstruct the original user input
+ std::string newseq = std::string("==") + get_userfunction(seq);
+ buf2keyseq(newseq.c_str(), dummy);
+ dummy.push_front('=');
+
+ v = &dummy;
+ }
+
+ for (keyseq::const_iterator i = v->begin(); i != v->end(); i++)
+ {
+ if (*i <= 32 || *i > 127) {
+ char buff[10];
+
+ snprintf( buff, sizeof(buff), "\\{%d}", *i );
+ s += std::string( buff );
+
+ // Removing the stringstream code because its highly
+ // non-portable. For starters, people and compilers
+ // are supposed to be using the <sstream> implementation
+ // (with the ostringstream class) not the old <strstream>
+ // version, but <sstream> is not as available as it should be.
+ //
+ // The strstream implementation isn't very standard
+ // either: some compilers require the "ends" marker,
+ // others don't (and potentially fatal errors can
+ // happen if you don't have it correct for the system...
+ // ie its hard to make portable). It also isn't a very
+ // good implementation to begin with.
+ //
+ // Everyone should have snprintf()... we supply a version
+ // in libutil.cc to make sure of that! -- bwr
+ //
+ // std::ostrstream ss;
+ // ss << "\\{" << *i << "}" << ends;
+ // s += ss.str();
+ } else if (*i == '\\') {
+ s += "\\\\";
+ } else {
+ s += *i;
+ }
+ }
+
+ return (s);
+}
+
+/*
+ * Add a macro (suprise, suprise).
+ */
+static void macro_add( macromap &mapref, keyseq key, keyseq action )
+{
+ mapref[key] = action;
+}
+
+static void macro_add( macromap &mapref, keyseq key, const char *buff )
+{
+ keyseq act;
+ buf2keyseq(buff, act);
+ if (!act.empty())
+ macro_add( mapref, key, act );
+}
+
+/*
+ * Remove a macro.
+ */
+static void macro_del( macromap &mapref, keyseq key )
+{
+ mapref.erase( key );
+}
+
+
+/*
+ * Adds keypresses from a sequence into the internal keybuffer. Ignores
+ * macros.
+ */
+static void macro_buf_add( keyseq actions )
+{
+ for (keyseq::iterator i = actions.begin(); i != actions.end(); i++)
+ Buffer.push_back(*i);
+}
+
+/*
+ * Adds a single keypress into the internal keybuffer.
+ */
+void macro_buf_add( int key )
+{
+ Buffer.push_back( key );
+}
+
+
+/*
+ * Adds keypresses from a sequence into the internal keybuffer. Does some
+ * O(N^2) analysis to the sequence to replace macros.
+ */
+static void macro_buf_add_long( keyseq actions, macromap &keymap = Keymaps[KC_DEFAULT] )
+{
+ keyseq tmp;
+
+ // debug << "Adding: " << vtostr(actions) << endl;
+ // debug.flush();
+
+ // Check whether any subsequences of the sequence are macros.
+ // The matching starts from as early as possible, and is
+ // as long as possible given the first constraint. I.e from
+ // the sequence "abcdef" and macros "ab", "bcde" and "de"
+ // "ab" and "de" are recognized as macros.
+
+ while (actions.size() > 0)
+ {
+ tmp = actions;
+
+ while (tmp.size() > 0)
+ {
+ keyseq result = keymap[tmp];
+
+ // Found a macro. Add the expansion (action) of the
+ // macro into the buffer.
+ if (result.size() > 0) {
+ macro_buf_add( result );
+ break;
+ }
+
+ // Didn't find a macro. Remove a key from the end
+ // of the sequence, and try again.
+ tmp.pop_back();
+ }
+
+ if (tmp.size() == 0) {
+ // Didn't find a macro. Add the first keypress of the sequence
+ // into the buffer, remove it from the sequence, and try again.
+ macro_buf_add( actions.front() );
+ actions.pop_front();
+
+ } else {
+ // Found a macro, which has already been added above. Now just
+ // remove the macroed keys from the sequence.
+ for (unsigned int i = 0; i < tmp.size(); i++)
+ actions.pop_front();
+ }
+ }
+}
+
+/*
+ * Command macros are only applied from the immediate front of the
+ * buffer, and only when the game is expecting a command.
+ */
+static void macro_buf_apply_command_macro( void )
+{
+ keyseq tmp = Buffer;
+
+ // find the longest match from the start of the buffer and replace it
+ while (tmp.size() > 0)
+ {
+ keyseq result = Macros[tmp];
+
+ if (result.size() > 0)
+ {
+ // Found macro, remove match from front:
+ for (unsigned int i = 0; i < tmp.size(); i++)
+ Buffer.pop_front();
+
+ // Add macro to front:
+ for (keyseq::reverse_iterator k = result.rbegin(); k != result.rend(); k++)
+ Buffer.push_front(*k);
+
+ break;
+ }
+
+ tmp.pop_back();
+ }
+}
+
+/*
+ * Removes the earlies keypress from the keybuffer, and returns its
+ * value. If buffer was empty, returns -1;
+ */
+static int macro_buf_get( void )
+{
+ if (Buffer.size() == 0)
+ return (-1);
+
+ int key = Buffer.front();
+ Buffer.pop_front();
+
+ return (key);
+}
+
+static void write_map(std::ofstream &f, const macromap &mp, const char *key)
+{
+ for (macromap::const_iterator i = mp.begin(); i != mp.end(); i++)
+ {
+ // Need this check, since empty values are added into the
+ // macro struct for all used keyboard commands.
+ if (i->second.size())
+ {
+ f << key << vtostr((*i).first) << std::endl
+ << "A:" << vtostr((*i).second) << std::endl << std::endl;
+ }
+ }
+}
+
+/*
+ * Saves macros into the macrofile, overwriting the old one.
+ */
+void macro_save( void )
+{
+ std::ofstream f;
+ f.open( get_macro_file().c_str() );
+
+ f << "# WARNING: This file is entirely auto-generated." << std::endl
+ << std::endl << "# Key Mappings:" << std::endl;
+ for (int mc = KC_DEFAULT; mc < KC_CONTEXT_COUNT; ++mc) {
+ char keybuf[30] = "K:";
+ if (mc)
+ snprintf(keybuf, sizeof keybuf, "K%d:", mc);
+ write_map(f, Keymaps[mc], keybuf);
+ }
+
+ f << "# Command Macros:" << std::endl;
+ write_map(f, Macros, "M:");
+
+ f.close();
+}
+
+/*
+ * Reads as many keypresses as are available (waiting for at least one),
+ * and returns them as a single keyseq.
+ */
+static keyseq getch_mul( int (*rgetch)() = NULL )
+{
+ keyseq keys;
+ int a;
+
+ if (!rgetch)
+ rgetch = getch;
+
+ keys.push_back( a = rgetch() );
+
+ // The a == 0 test is legacy code that I don't dare to remove. I
+ // have a vague recollection of it being a kludge for conio support.
+ while ((kbhit() || a == 0)) {
+ keys.push_back( a = rgetch() );
+ }
+
+ return (keys);
+}
+
+/*
+ * Replacement for getch(). Returns keys from the key buffer if available.
+ * If not, adds some content to the buffer, and returns some of it.
+ */
+int getchm( int (*rgetch)() )
+{
+ return getchm( KC_DEFAULT, rgetch );
+}
+
+int getchm(KeymapContext mc, int (*rgetch)())
+{
+ int a;
+
+ // Got data from buffer.
+ if ((a = macro_buf_get()) != -1)
+ return (a);
+
+ // Read some keys...
+ keyseq keys = getch_mul(rgetch);
+ // ... and add them into the buffer
+ macro_buf_add_long( keys, Keymaps[mc] );
+
+ return (macro_buf_get());
+}
+
+/*
+ * Replacement for getch(). Returns keys from the key buffer if available.
+ * If not, adds some content to the buffer, and returns some of it.
+ */
+int getch_with_command_macros( void )
+{
+ if (Buffer.size() == 0)
+ {
+ // Read some keys...
+ keyseq keys = getch_mul();
+ // ... and add them into the buffer (apply keymaps)
+ macro_buf_add_long( keys );
+ }
+
+ // Apply longest matching macro at front of buffer:
+ macro_buf_apply_command_macro();
+
+ return (macro_buf_get());
+}
+
+/*
+ * Flush the buffer. Later we'll probably want to give the player options
+ * as to when this happens (ex. always before command input, casting failed).
+ */
+void flush_input_buffer( int reason )
+{
+ if (Options.flush_input[ reason ])
+ Buffer.clear();
+}
+
+void macro_add_query( void )
+{
+ unsigned char input;
+ bool keymap = false;
+ KeymapContext keymc = KC_DEFAULT;
+
+ mesclr();
+ mpr( "Command (m)acro or keymap [(k) default, (x) level-map or "
+ "(t)argeting]? ", MSGCH_PROMPT );
+ input = getch();
+ if (input == 0)
+ input = getch();
+
+ input = tolower( input );
+ if (input == 'k')
+ {
+ keymap = true;
+ keymc = KC_DEFAULT;
+ }
+ else if (input == 'x')
+ {
+ keymap = true;
+ keymc = KC_LEVELMAP;
+ }
+ else if (input == 't')
+ {
+ keymap = true;
+ keymc = KC_TARGETING;
+ }
+ else if (input == 'm')
+ keymap = false;
+ else
+ {
+ mpr( "Aborting." );
+ return;
+ }
+
+ // reference to the appropriate mapping
+ macromap &mapref = (keymap ? Keymaps[keymc] : Macros);
+
+ snprintf( info, INFO_SIZE, "Input %s%s trigger key: ",
+ keymap?
+ (keymc == KC_DEFAULT? "default " :
+ keymc == KC_LEVELMAP? "level-map " :
+ "targeting ") :
+ "",
+ (keymap ? "keymap" : "macro") );
+
+ mpr( info, MSGCH_PROMPT );
+ keyseq key = getch_mul();
+
+ cprintf( "%s" EOL, (vtostr( key )).c_str() ); // echo key to screen
+
+ if (mapref[key].size() > 0)
+ {
+ snprintf( info, INFO_SIZE, "Current Action: %s",
+ (vtostr( mapref[key] )).c_str() );
+
+ mpr( info, MSGCH_WARN );
+ mpr( "Do you wish to (r)edefine, (c)lear, or (a)bort?", MSGCH_PROMPT );
+
+ input = getch();
+ if (input == 0)
+ input = getch();
+
+ input = tolower( input );
+ if (input == 'a' || input == ESCAPE)
+ {
+ mpr( "Aborting." );
+ return;
+ }
+ else if (input == 'c')
+ {
+ mpr( "Cleared." );
+ macro_del( mapref, key );
+ return;
+ }
+ }
+
+ mpr( "Input Macro Action: ", MSGCH_PROMPT );
+
+ // Using getch_mul() here isn't very useful... We'd like the
+ // flexibility to define multicharacter macros without having
+ // to resort to editing files and restarting the game. -- bwr
+ // keyseq act = getch_mul();
+
+ char buff[4096];
+ get_input_line(buff, sizeof buff);
+
+ if (Options.macro_meta_entry)
+ {
+ macro_add( mapref, key, parse_keyseq(buff) );
+ }
+ else
+ {
+ macro_add( mapref, key, buff );
+ }
+
+ redraw_screen();
+}
+
+
+/*
+ * Initializes the macros.
+ */
+int macro_init( void )
+{
+ std::string s;
+ std::ifstream f;
+ keyseq key, action;
+ bool keymap = false;
+ KeymapContext keymc = KC_DEFAULT;
+
+ f.open( get_macro_file().c_str() );
+
+ while (f >> s)
+ {
+ trim_string(s); // remove white space from ends
+
+ if (s[0] == '#') {
+ continue; // skip comments
+
+ } else if (s.substr(0, 2) == "K:") {
+ key = parse_keyseq(s.substr(2));
+ keymap = true;
+ keymc = KC_DEFAULT;
+
+ } else if (s.length() >= 3 && s[0] == 'K' && s[2] == ':') {
+ keymc = KeymapContext( KC_DEFAULT + s[1] - '0' );
+ if (keymc >= KC_DEFAULT && keymc < KC_CONTEXT_COUNT) {
+ key = parse_keyseq(s.substr(3));
+ keymap = true;
+ }
+
+ } else if (s.substr(0, 2) == "M:") {
+ key = parse_keyseq(s.substr(2));
+ keymap = false;
+
+ } else if (s.substr(0, 2) == "A:") {
+ action = parse_keyseq(s.substr(2));
+ macro_add( (keymap ? Keymaps[keymc] : Macros), key, action );
+ }
+ }
+
+ return (0);
+}
+
+#ifdef CLUA_BINDINGS
+void macro_userfn(const char *keys, const char *regname)
+{
+ // TODO: Implement.
+ // Converting 'keys' to a key sequence is the difficulty. Doing it portably
+ // requires a mapping of key names to whatever getch() spits back, unlikely
+ // to happen in a hurry.
+}
+#endif
+
+#endif