640 lines
18 KiB
C++
640 lines
18 KiB
C++
// console.cpp: the console buffer, its display, and command line control
|
|
|
|
#include "cube.h"
|
|
|
|
#define CONSPAD (FONTH/3)
|
|
|
|
VARP(altconsize, 0, 0, 100);
|
|
VARP(fullconsize, 0, 40, 100);
|
|
VARP(consize, 0, 6, 100);
|
|
VARP(confade, 0, 20, 60);
|
|
VARP(conalpha, 0, 255, 255);
|
|
VAR(conopen, 0, 0, 1);
|
|
VAR(numconlines, 0, 0, 1);
|
|
|
|
struct console : consolebuffer<cline>
|
|
{
|
|
int conskip;
|
|
void setconskip(int n)
|
|
{
|
|
int visible_lines = (int)(min(fullconsole ? ((VIRTH*2 - 2*CONSPAD - 2*FONTH/3)*(fullconsole==1 ? altconsize : fullconsize))/100 : FONTH*consize, (VIRTH*2 - 2*CONSPAD - 2*FONTH/3))/ (CONSPAD + 2*FONTH/3)) - 1;
|
|
conskip = clamp(conskip + n, 0, clamp(conlines.length()-visible_lines, 0, conlines.length()));
|
|
}
|
|
|
|
static const int WORDWRAP = 80;
|
|
|
|
int fullconsole;
|
|
void toggleconsole()
|
|
{
|
|
if(!fullconsole) fullconsole = altconsize ? 1 : 2;
|
|
else fullconsole = (fullconsole + 1) % 3;
|
|
conopen = fullconsole;
|
|
}
|
|
|
|
void addline(const char *sf) { consolebuffer<cline>::addline(sf, totalmillis); numconlines++; }
|
|
|
|
void render()
|
|
{
|
|
int conwidth = (fullconsole ? VIRTW : int(floor(getradarpos().x)))*2 - 2*CONSPAD - 2*FONTH/3;
|
|
int h = VIRTH*2 - 2*CONSPAD - 2*FONTH/3;
|
|
int conheight = min(fullconsole ? (h*(fullconsole==1 ? altconsize : fullconsize))/100 : FONTH*consize, h);
|
|
|
|
if(fullconsole) blendbox(CONSPAD, CONSPAD, conwidth+CONSPAD+2*FONTH/3, conheight+CONSPAD+2*FONTH/3, true);
|
|
|
|
int numl = conlines.length(), offset = min(conskip, numl);
|
|
|
|
if(!fullconsole && confade)
|
|
{
|
|
if(!conskip)
|
|
{
|
|
numl = 0;
|
|
loopvrev(conlines) if(totalmillis-conlines[i].millis < confade*1000) { numl = i+1; break; }
|
|
}
|
|
else offset--;
|
|
}
|
|
|
|
int y = 0;
|
|
loopi(numl) //determine visible height
|
|
{
|
|
// shuffle backwards to fill if necessary
|
|
int idx = offset+i < numl ? offset+i : --offset;
|
|
char *line = conlines[idx].line;
|
|
int width, height;
|
|
text_bounds(line, width, height, conwidth);
|
|
y += height;
|
|
if(y > conheight) { numl = i; if(offset == idx) ++offset; break; }
|
|
}
|
|
y = CONSPAD+FONTH/3;
|
|
loopi(numl)
|
|
{
|
|
int idx = offset + numl-i-1;
|
|
char *line = conlines[idx].line;
|
|
draw_text(line, CONSPAD+FONTH/3, y, 0xFF, 0xFF, 0xFF, fullconsole ? 0xFF : conalpha, -1, conwidth);
|
|
int width, height;
|
|
text_bounds(line, width, height, conwidth);
|
|
y += height;
|
|
}
|
|
}
|
|
|
|
console() : consolebuffer<cline>(200), fullconsole(false) {}
|
|
};
|
|
|
|
console con;
|
|
textinputbuffer cmdline;
|
|
char *cmdaction = NULL, *cmdprompt = NULL;
|
|
bool saycommandon = false;
|
|
|
|
VARFP(maxcon, 10, 200, 1000, con.setmaxlines(maxcon));
|
|
|
|
void setconskip(int *n) { con.setconskip(*n); }
|
|
COMMANDN(conskip, setconskip, "i");
|
|
|
|
void toggleconsole() { con.toggleconsole(); }
|
|
COMMANDN(toggleconsole, toggleconsole, "");
|
|
|
|
void renderconsole() { con.render(); }
|
|
|
|
void clientlogf(const char *s, ...)
|
|
{
|
|
defvformatstring(sp, s, s);
|
|
filtertext(sp, sp, 2);
|
|
extern struct servercommandline scl;
|
|
const char *ts = scl.logtimestamp ? timestring(true, "%b %d %H:%M:%S ") : "";
|
|
char *p, *l = sp;
|
|
do
|
|
{ // break into single lines first
|
|
if((p = strchr(l, '\n'))) *p = '\0';
|
|
printf("%s%s\n", ts, l);
|
|
l = p + 1;
|
|
}
|
|
while(p);
|
|
}
|
|
SVAR(conline,"n/a");
|
|
void conoutf(const char *s, ...)
|
|
{
|
|
defvformatstring(sf, s, s);
|
|
clientlogf("%s", sf);
|
|
con.addline(sf);
|
|
delete[] conline; conline=newstring(sf);
|
|
}
|
|
|
|
COMMANDF(strstr, "ss", (char *a, char *b) { intret(strstr(a, b) ? 1 : 0); });
|
|
|
|
int rendercommand(int x, int y, int w)
|
|
{
|
|
const char *useprompt = cmdprompt ? cmdprompt : "#";
|
|
defformatstring(s)("%s %s", useprompt, cmdline.buf);
|
|
int width, height;
|
|
text_bounds(s, width, height, w);
|
|
y -= height - FONTH;
|
|
draw_text(s, x, y, 0xFF, 0xFF, 0xFF, 0xFF, cmdline.pos>=0 ? cmdline.pos + strlen(useprompt) + 1 : (int)strlen(s), w);
|
|
return height;
|
|
}
|
|
|
|
const char *getCONprefix(int n)
|
|
{
|
|
const char* CONpreSTR[] = {
|
|
">", // ">>>", // "TALK" // "T" // ">"
|
|
"/", // "CFG", // "EXEC" // "!" // "!"
|
|
"%", // "TEAM" // ">" // "T"
|
|
};
|
|
return (n>=0 && size_t(n) < sizeof(CONpreSTR)/sizeof(CONpreSTR[0])) ? CONpreSTR[n] : "#";
|
|
}
|
|
|
|
int getCONlength(int n)
|
|
{
|
|
const char* CURpreSTR = getCONprefix(n);
|
|
return strlen(CURpreSTR);
|
|
}
|
|
|
|
/** WIP ALERT */
|
|
int rendercommand_wip(int x, int y, int w)
|
|
{
|
|
int width, height = 0;
|
|
if( strlen(cmdline.buf) > 0 )
|
|
{
|
|
int ctx = -1;
|
|
switch( cmdline.buf[0] )
|
|
{
|
|
case '>': ctx = 0; break;
|
|
case '/': ctx = 1; break;
|
|
case '%': ctx = 2; break;
|
|
default: break;
|
|
}
|
|
defformatstring(s)("%s %s", getCONprefix(ctx), cmdline.buf+1);
|
|
text_bounds(s, width, height, w);
|
|
y -= height - FONTH;
|
|
draw_text(s, x, y, 0xFF, 0xFF, 0xFF, 0xFF, cmdline.pos>=0 ? cmdline.pos/*+1*/+getCONlength(ctx) : (int)strlen(s), w);
|
|
}
|
|
return height;
|
|
}
|
|
|
|
// keymap is defined externally in keymap.cfg
|
|
|
|
vector<keym> keyms;
|
|
|
|
const char *keycmds[keym::NUMACTIONS] = { "bind", "specbind", "editbind" };
|
|
inline const char *keycmd(int type) { return type >= 0 && type < keym::NUMACTIONS ? keycmds[type] : ""; }
|
|
|
|
void keymap(int *code, char *key)
|
|
{
|
|
keym &km = keyms.add();
|
|
km.code = *code;
|
|
km.name = newstring(key);
|
|
}
|
|
|
|
COMMAND(keymap, "is");
|
|
|
|
keym *findbind(const char *key)
|
|
{
|
|
loopv(keyms) if(!strcasecmp(keyms[i].name, key)) return &keyms[i];
|
|
return NULL;
|
|
}
|
|
|
|
keym *findbinda(const char *action, int type)
|
|
{
|
|
loopv(keyms) if(!strcasecmp(keyms[i].actions[type], action)) return &keyms[i];
|
|
return NULL;
|
|
}
|
|
|
|
keym *findbindc(int code)
|
|
{
|
|
loopv(keyms) if(keyms[i].code==code) return &keyms[i];
|
|
return NULL;
|
|
}
|
|
|
|
void findkey(int *code)
|
|
{
|
|
for (int i = 0; i < keyms.length(); i++)
|
|
{
|
|
if(keyms[i].code==*code)
|
|
{
|
|
defformatstring(out)("%s", keyms[i].name);
|
|
result(out);
|
|
return;
|
|
}
|
|
}
|
|
result("-255");
|
|
return;
|
|
}
|
|
|
|
void findkeycode(const char* s)
|
|
{
|
|
for (int i = 0; i < keyms.length(); i++)
|
|
{
|
|
if(strcmp(s, keyms[i].name) == 0)
|
|
{
|
|
defformatstring(out)("%i", keyms[i].code);
|
|
result(out);
|
|
return;
|
|
}
|
|
}
|
|
result("-255");
|
|
return;
|
|
}
|
|
|
|
COMMAND(findkey, "i");
|
|
COMMAND(findkeycode, "s");
|
|
|
|
keym *keypressed = NULL;
|
|
char *keyaction = NULL;
|
|
|
|
bool bindkey(keym *km, const char *action, int type)
|
|
{
|
|
if(!km) return false;
|
|
if(type < keym::ACTION_DEFAULT || type >= keym::NUMACTIONS) { conoutf("invalid bind type \"%i\"", type); return false; }
|
|
if(!keypressed || keyaction!=km->actions[type]) delete[] km->actions[type];
|
|
km->actions[type] = newstring(action);
|
|
return true;
|
|
}
|
|
|
|
void bindk(const char *key, const char *action, int type)
|
|
{
|
|
keym *km = findbind(key);
|
|
if(!km) { conoutf("unknown key \"%s\"", key); return; }
|
|
bindkey(km, action, type);
|
|
}
|
|
|
|
void keybind(const char *key, int type)
|
|
{
|
|
keym *km = findbind(key);
|
|
if(!km) { conoutf("unknown key \"%s\"", key); return; }
|
|
if(type < keym::ACTION_DEFAULT || type >= keym::NUMACTIONS) { conoutf("invalid bind type \"%i\"", type); return; }
|
|
result(km->actions[type]);
|
|
}
|
|
|
|
bool bindc(int code, const char *action, int type)
|
|
{
|
|
keym *km = findbindc(code);
|
|
if(km) return bindkey(km, action, type);
|
|
else return false;
|
|
}
|
|
|
|
void searchbinds(const char *action, int type)
|
|
{
|
|
if(!action || !action[0]) return;
|
|
if(type < keym::ACTION_DEFAULT || type >= keym::NUMACTIONS) { conoutf("invalid bind type \"%i\"", type); return; }
|
|
vector<char> names;
|
|
loopv(keyms)
|
|
{
|
|
if(!strcmp(keyms[i].actions[type], action))
|
|
{
|
|
if(names.length()) names.add(' ');
|
|
names.put(keyms[i].name, strlen(keyms[i].name));
|
|
}
|
|
}
|
|
names.add('\0');
|
|
result(names.getbuf());
|
|
}
|
|
|
|
COMMANDF(keybind, "s", (const char *key) { keybind(key, keym::ACTION_DEFAULT); } );
|
|
COMMANDF(keyspecbind, "s", (const char *key) { keybind(key, keym::ACTION_SPECTATOR); } );
|
|
COMMANDF(keyeditbind, "s", (const char *key) { keybind(key, keym::ACTION_EDITING); } );
|
|
|
|
COMMANDF(bind, "ss", (const char *key, const char *action) { bindk(key, action, keym::ACTION_DEFAULT); } );
|
|
COMMANDF(specbind, "ss", (const char *key, const char *action) { bindk(key, action, keym::ACTION_SPECTATOR); } );
|
|
COMMANDF(editbind, "ss", (const char *key, const char *action) { bindk(key, action, keym::ACTION_EDITING); } );
|
|
|
|
COMMANDF(searchbinds, "s", (const char *action) { searchbinds(action, keym::ACTION_DEFAULT); });
|
|
COMMANDF(searchspecbinds, "s", (const char *action) { searchbinds(action, keym::ACTION_SPECTATOR); });
|
|
COMMANDF(searcheditbinds, "s", (const char *action) { searchbinds(action, keym::ACTION_EDITING); });
|
|
|
|
struct releaseaction
|
|
{
|
|
keym *key;
|
|
char *action;
|
|
};
|
|
vector<releaseaction> releaseactions;
|
|
|
|
char *addreleaseaction(const char *s)
|
|
{
|
|
if(!keypressed) return NULL;
|
|
releaseaction &ra = releaseactions.add();
|
|
ra.key = keypressed;
|
|
ra.action = newstring(s);
|
|
return keypressed->name;
|
|
}
|
|
|
|
void onrelease(char *s)
|
|
{
|
|
addreleaseaction(s);
|
|
}
|
|
|
|
COMMAND(onrelease, "s");
|
|
|
|
void saycommand(char *init) // turns input to the command line on or off
|
|
{
|
|
SDL_EnableUNICODE(saycommandon = (init!=NULL));
|
|
setscope(false);
|
|
setburst(false);
|
|
if(!editmode) keyrepeat(saycommandon);
|
|
copystring(cmdline.buf, init ? init : ">"); // ALL cmdline.buf[0] ARE flag-chars ! ">" is for talk - the previous "no flag-char" item
|
|
DELETEA(cmdaction);
|
|
DELETEA(cmdprompt);
|
|
cmdline.pos = -1;
|
|
}
|
|
|
|
void inputcommand(char *init, char *action, char *prompt)
|
|
{
|
|
saycommand(init);
|
|
if(action[0]) cmdaction = newstring(action);
|
|
if(prompt[0]) cmdprompt = newstring(prompt);
|
|
}
|
|
|
|
void mapmsg(char *s)
|
|
{
|
|
string text;
|
|
filterrichtext(text, s);
|
|
filterservdesc(text, text);
|
|
copystring(hdr.maptitle, text, 128);
|
|
}
|
|
|
|
void getmapmsg(void)
|
|
{
|
|
string text;
|
|
copystring(text, hdr.maptitle, 128);
|
|
result(text);
|
|
}
|
|
|
|
COMMAND(saycommand, "c");
|
|
COMMAND(inputcommand, "sss");
|
|
COMMAND(mapmsg, "s");
|
|
COMMAND(getmapmsg, "");
|
|
|
|
#if !defined(WIN32) && !defined(__APPLE__)
|
|
#include <X11/Xlib.h>
|
|
#include <SDL_syswm.h>
|
|
#endif
|
|
|
|
void pasteconsole(char *dst)
|
|
{
|
|
#ifdef WIN32
|
|
if(!IsClipboardFormatAvailable(CF_TEXT)) return;
|
|
if(!OpenClipboard(NULL)) return;
|
|
char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT));
|
|
concatstring(dst, cb);
|
|
GlobalUnlock(cb);
|
|
CloseClipboard();
|
|
#elif defined(__APPLE__)
|
|
extern void mac_pasteconsole(char *commandbuf);
|
|
|
|
mac_pasteconsole(dst);
|
|
#else
|
|
SDL_SysWMinfo wminfo;
|
|
SDL_VERSION(&wminfo.version);
|
|
wminfo.subsystem = SDL_SYSWM_X11;
|
|
if(!SDL_GetWMInfo(&wminfo)) return;
|
|
int cbsize;
|
|
char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize);
|
|
if(!cb || !cbsize) return;
|
|
int commandlen = strlen(dst);
|
|
for(char *cbline = cb, *cbend; commandlen + 1 < MAXSTRLEN && cbline < &cb[cbsize]; cbline = cbend + 1)
|
|
{
|
|
cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline);
|
|
if(!cbend) cbend = &cb[cbsize];
|
|
if(commandlen + cbend - cbline + 1 > MAXSTRLEN) cbend = cbline + MAXSTRLEN - commandlen - 1;
|
|
memcpy(&dst[commandlen], cbline, cbend - cbline);
|
|
commandlen += cbend - cbline;
|
|
dst[commandlen] = '\n';
|
|
if(commandlen + 1 < MAXSTRLEN && cbend < &cb[cbsize]) ++commandlen;
|
|
dst[commandlen] = '\0';
|
|
}
|
|
XFree(cb);
|
|
#endif
|
|
}
|
|
|
|
struct hline
|
|
{
|
|
char *buf, *action, *prompt;
|
|
|
|
hline() : buf(NULL), action(NULL), prompt(NULL) {}
|
|
~hline()
|
|
{
|
|
DELETEA(buf);
|
|
DELETEA(action);
|
|
DELETEA(prompt);
|
|
}
|
|
|
|
void restore()
|
|
{
|
|
copystring(cmdline.buf, buf);
|
|
if(cmdline.pos >= (int)strlen(cmdline.buf)) cmdline.pos = -1;
|
|
DELETEA(cmdaction);
|
|
DELETEA(cmdprompt);
|
|
if(action) cmdaction = newstring(action);
|
|
if(prompt) cmdprompt = newstring(prompt);
|
|
}
|
|
|
|
bool shouldsave()
|
|
{
|
|
return strcmp(cmdline.buf, buf) ||
|
|
(cmdaction ? !action || strcmp(cmdaction, action) : action!=NULL) ||
|
|
(cmdprompt ? !prompt || strcmp(cmdprompt, prompt) : prompt!=NULL);
|
|
}
|
|
|
|
void save()
|
|
{
|
|
buf = newstring(cmdline.buf);
|
|
if(cmdaction) action = newstring(cmdaction);
|
|
if(cmdprompt) prompt = newstring(cmdprompt);
|
|
}
|
|
|
|
void run()
|
|
{
|
|
pushscontext(IEXC_PROMPT);
|
|
if(action)
|
|
{
|
|
alias("cmdbuf", buf);
|
|
execute(action);
|
|
}
|
|
else if(buf[0]=='/') execute(buf+1);
|
|
else if(buf[0]=='>') toserver(buf+1);
|
|
else if(buf[0]=='%') toserver(buf);
|
|
else toserver(buf); // execute(buf); // still default to simple "say".
|
|
popscontext();
|
|
}
|
|
};
|
|
vector<hline *> history;
|
|
int histpos = 0;
|
|
|
|
VARP(maxhistory, 0, 1000, 10000);
|
|
|
|
void history_(int *n)
|
|
{
|
|
static bool inhistory = false;
|
|
if(!inhistory && history.inrange(*n))
|
|
{
|
|
inhistory = true;
|
|
history[history.length() - *n - 1]->run();
|
|
inhistory = false;
|
|
}
|
|
}
|
|
|
|
COMMANDN(history, history_, "i");
|
|
|
|
void savehistory()
|
|
{
|
|
stream *f = openfile(path("config/history", true), "w");
|
|
if(!f) return;
|
|
loopv(history)
|
|
{
|
|
f->printf("%s\n",history[i]->buf);
|
|
}
|
|
delete f;
|
|
}
|
|
|
|
void loadhistory()
|
|
{
|
|
char *histbuf = loadfile(path("config/history", true), NULL);
|
|
if(!histbuf) return;
|
|
char *line = NULL;
|
|
line = strtok(histbuf, "\n");
|
|
while(line)
|
|
{
|
|
history.add(new hline)->buf = newstring(line);
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
DELETEA(histbuf);
|
|
histpos = history.length();
|
|
}
|
|
|
|
void execbind(keym &k, bool isdown)
|
|
{
|
|
loopv(releaseactions)
|
|
{
|
|
releaseaction &ra = releaseactions[i];
|
|
if(ra.key==&k)
|
|
{
|
|
if(!isdown) execute(ra.action);
|
|
delete[] ra.action;
|
|
releaseactions.remove(i--);
|
|
}
|
|
}
|
|
if(isdown)
|
|
{
|
|
int state = keym::ACTION_DEFAULT;
|
|
if(editmode) state = keym::ACTION_EDITING;
|
|
else if(player1->isspectating()) state = keym::ACTION_SPECTATOR;
|
|
char *&action = k.actions[state][0] ? k.actions[state] : k.actions[keym::ACTION_DEFAULT];
|
|
keyaction = action;
|
|
keypressed = &k;
|
|
execute(keyaction);
|
|
keypressed = NULL;
|
|
if(keyaction!=action) delete[] keyaction;
|
|
}
|
|
k.pressed = isdown;
|
|
}
|
|
|
|
void consolekey(int code, bool isdown, int cooked)
|
|
{
|
|
if(isdown)
|
|
{
|
|
switch(code)
|
|
{
|
|
case SDLK_F1:
|
|
toggledoc();
|
|
break;
|
|
|
|
case SDLK_F2:
|
|
scrolldoc(-4);
|
|
break;
|
|
|
|
case SDLK_F3:
|
|
scrolldoc(4);
|
|
break;
|
|
|
|
case SDL_AC_BUTTON_WHEELUP:
|
|
case SDLK_UP:
|
|
if(histpos > history.length()) histpos = history.length();
|
|
if(histpos > 0) history[--histpos]->restore();
|
|
break;
|
|
|
|
case SDL_AC_BUTTON_WHEELDOWN:
|
|
case SDLK_DOWN:
|
|
if(histpos + 1 < history.length()) history[++histpos]->restore();
|
|
break;
|
|
|
|
case SDLK_TAB:
|
|
if(!cmdaction)
|
|
{
|
|
complete(cmdline.buf);
|
|
if(cmdline.pos>=0 && cmdline.pos>=(int)strlen(cmdline.buf)) cmdline.pos = -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
resetcomplete();
|
|
cmdline.key(code, isdown, cooked);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(code==SDLK_RETURN || code==SDLK_KP_ENTER || code==SDL_AC_BUTTON_LEFT || code==SDL_AC_BUTTON_MIDDLE)
|
|
{
|
|
// make laptop users happy; LMB shall only work with history
|
|
if(code == SDL_AC_BUTTON_LEFT && histpos == history.length()) return;
|
|
|
|
hline *h = NULL;
|
|
if(cmdline.buf[0])
|
|
{
|
|
if(history.empty() || history.last()->shouldsave())
|
|
{
|
|
if(maxhistory && history.length() >= maxhistory)
|
|
{
|
|
loopi(history.length()-maxhistory+1) delete history[i];
|
|
history.remove(0, history.length()-maxhistory+1);
|
|
}
|
|
history.add(h = new hline)->save();
|
|
}
|
|
else h = history.last();
|
|
}
|
|
histpos = history.length();
|
|
saycommand(NULL);
|
|
if(h) h->run();
|
|
}
|
|
else if(code==SDLK_ESCAPE || code== SDL_AC_BUTTON_RIGHT)
|
|
{
|
|
histpos = history.length();
|
|
saycommand(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void keypress(int code, bool isdown, int cooked, SDLMod mod)
|
|
{
|
|
keym *haskey = NULL;
|
|
loopv(keyms) if(keyms[i].code==code) { haskey = &keyms[i]; break; }
|
|
if(haskey && haskey->pressed) execbind(*haskey, isdown); // allow pressed keys to release
|
|
else if(saycommandon) consolekey(code, isdown, cooked); // keystrokes go to commandline
|
|
else if(!menukey(code, isdown, cooked, mod)) // keystrokes go to menu
|
|
{
|
|
if(haskey) execbind(*haskey, isdown);
|
|
}
|
|
if(isdown && identexists("KEYPRESS")) // TODO: Remove this if its misued. e.x: /KEYPRESS = [ echo You pressed key code: $arg1 ] // Output: You pressed key code: 32 (if you press the spacebar)
|
|
{
|
|
defformatstring(kpi)("KEYPRESS %d", code);
|
|
execute(kpi);
|
|
}
|
|
if (!isdown && identexists("KEYRELEASE"))
|
|
{
|
|
defformatstring(kpo)("KEYRELEASE %d", code);
|
|
execute(kpo);
|
|
}
|
|
}
|
|
|
|
char *getcurcommand()
|
|
{
|
|
return saycommandon ? cmdline.buf : NULL;
|
|
}
|
|
|
|
void writebinds(stream *f)
|
|
{
|
|
loopv(keyms)
|
|
{
|
|
keym *km = &keyms[i];
|
|
loopj(3) if(*km->actions[j]) f->printf("%s \"%s\" [%s]\n", keycmd(j), km->name, km->actions[j]);
|
|
}
|
|
}
|
|
|