Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/game.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,12 @@ copymapcfg = [
]
]

chatsay = [ inputcommand "" [ say $commandbuf ] "[Chat]" s ]
chatsay = [ inputcommand "" [ say $commandbuf ] "[Chat]" n ]
chatteam = [
if (|| $isspectator [= $getteam 0]) [
chatsay
] [
inputcommand "" [sayteam $commandbuf] (+s "[" $getteamtextcode "Team Chat^f7]") s
inputcommand "" [sayteam $commandbuf] (+s "[" $getteamtextcode "Team Chat^f7]") n
]
]
chatexec = [ inputcommand "" [ commandbuf ] "[^f4Console^f7]" s ]
Expand Down
98 changes: 75 additions & 23 deletions source/engine/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ reversequeue<cline, MAXCONLINES> conlines;
int commandmillis = -1;
string commandbuf;
char *commandaction = NULL, *commandprompt = NULL;
enum { CF_COMPLETE = 1<<0, CF_EXECUTE = 1<<1 };
enum { CF_COMPLETE = 1<<0, CF_EXECUTE = 1<<1, CF_NAMECOMPLETE = 1<<2 };
int commandflags = 0, commandpos = -1;

VARFP(maxcon, 10, 200, MAXCONLINES, { while(conlines.length() > maxcon) delete[] conlines.pop().line; });
Expand Down Expand Up @@ -368,6 +368,7 @@ void inputcommand(char *init, char *action = NULL, char *prompt = NULL, char *fl
case 'c': commandflags |= CF_COMPLETE; break;
case 'x': commandflags |= CF_EXECUTE; break;
case 's': commandflags |= CF_COMPLETE|CF_EXECUTE; break;
case 'n': commandflags |= CF_COMPLETE|CF_EXECUTE|CF_NAMECOMPLETE; break;
}
else if(init) commandflags |= CF_COMPLETE|CF_EXECUTE;
if(!isfullconsoletoggled && fullconsolecommand)
Expand Down Expand Up @@ -615,6 +616,26 @@ static char *skipwordrev(char *s, int n = -1)
return e+1;
}

/* don't try to complete names if:
- first character is a slash (already typing a command) or the null character (empty string)
- the cursor is at position 0 (there must be a '@' character before the cursor)
- there are spaces between the '@' symbol and the cursor (the '@mention' must be a single word)
*/
static bool may_complete_names()
{
if(commandbuf[0] == '/' || !commandbuf[0] || commandpos == 0) return false;
for(const char *p = &commandbuf[commandpos > 0 ? (commandpos-1) : (strlen(commandbuf)-1)]; p >= commandbuf; --p)
{
if(isspace(*p)) return false;
if(*p == '@')
{
if(p == commandbuf || isspace(p[-1])) return true;
continue; // the '@' symbol is not at the start of the buffer and does not follow a space: it could be part of the name, keep searching
}
}
return false;
}

bool consolekey(int code, bool isdown)
{
if(commandmillis < 0) return false;
Expand Down Expand Up @@ -703,9 +724,13 @@ bool consolekey(int code, bool isdown)
break;

case SDLK_TAB:
if(commandflags&CF_COMPLETE)
if(commandflags&CF_NAMECOMPLETE && may_complete_names()) // disable name completion if typing a command
{
complete(commandbuf, sizeof(commandbuf), commandflags&CF_EXECUTE ? "/" : NULL);
complete(commandbuf, sizeof(commandbuf), NULL, true);
}
else if(commandflags&CF_COMPLETE)
{
complete(commandbuf, sizeof(commandbuf), commandflags&CF_EXECUTE ? "/" : NULL, false);
if(commandpos>=0 && commandpos>=(int)strlen(commandbuf)) commandpos = -1;
}
break;
Expand Down Expand Up @@ -924,7 +949,7 @@ COMMANDN(complete, addfilecomplete, "sss");
COMMANDN(varcomplete, addvarcomplete, "sss");
COMMANDN(listcomplete, addlistcomplete, "ss");

void complete(char *s, int maxlen, const char *cmdprefix)
void complete(char *s, int maxlen, const char *cmdprefix, bool names)
{
int cmdlen = 0;
if(cmdprefix)
Expand All @@ -935,34 +960,61 @@ void complete(char *s, int maxlen, const char *cmdprefix)
if(!s[cmdlen]) return;
if(!completesize) { completesize = (int)strlen(&s[cmdlen]); DELETEA(lastcomplete); }

const char *nextcomplete = NULL;

if(names)
{
const char *last_at = NULL;
for(const char *p = s; *p; ++p)
{
if(*p == '@' && (p == s || isspace(p[-1]))) last_at = p;
}
if(last_at) // complete using player names
{
cmdprefix = s;
cmdlen = last_at - s + 1;
char *end = strchr(&s[cmdlen], ' ');
const char *found = game::completename(&s[last_at - s + 1], completesize-cmdlen-(end ? strlen(end) : 0), lastcomplete, nextcomplete);
if(found)
{
char *found_nospace = newstring(found);
for(char *p = found_nospace; *p; ++p) if(*p == ' ') *p = '_';
nextcomplete = end ? tempformatstring("%s%s", found_nospace, end) : found_nospace;
delete[] found_nospace;
}
}
}

filesval *f = NULL;
if(completesize)
if(completesize && !names)
{
char *end = strchr(&s[cmdlen], ' ');
if(end) f = completions.find(stringslice(&s[cmdlen], end), NULL);
}

const char *nextcomplete = NULL;
if(f) // complete using filenames
if(!names)
{
int commandsize = strchr(&s[cmdlen], ' ')+1-s;
f->update();
loopv(f->files)
if(f) // complete using filenames
{
if(strncmp(f->files[i], &s[commandsize], completesize+cmdlen-commandsize)==0 &&
(!lastcomplete || strcmp(f->files[i], lastcomplete) > 0) && (!nextcomplete || strcmp(f->files[i], nextcomplete) < 0))
nextcomplete = f->files[i];
int commandsize = strchr(&s[cmdlen], ' ')+1-s;
f->update();
loopv(f->files)
{
if(strncmp(f->files[i], &s[commandsize], completesize+cmdlen-commandsize)==0 &&
(!lastcomplete || strcmp(f->files[i], lastcomplete) > 0) && (!nextcomplete || strcmp(f->files[i], nextcomplete) < 0))
nextcomplete = f->files[i];
}
cmdprefix = s;
cmdlen = commandsize;
}
else // complete using command names
{
enumerate(idents, ident, id,
if(strncmp(id.name, &s[cmdlen], completesize)==0 &&
(!lastcomplete || strcmp(id.name, lastcomplete) > 0) && (!nextcomplete || strcmp(id.name, nextcomplete) < 0))
nextcomplete = id.name;
);
}
cmdprefix = s;
cmdlen = commandsize;
}
else // complete using command names
{
enumerate(idents, ident, id,
if(strncmp(id.name, &s[cmdlen], completesize)==0 &&
(!lastcomplete || strcmp(id.name, lastcomplete) > 0) && (!nextcomplete || strcmp(id.name, nextcomplete) < 0))
nextcomplete = id.name;
);
}
DELETEA(lastcomplete);
if(nextcomplete)
Expand Down
2 changes: 1 addition & 1 deletion source/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ extern float renderconsole(float w, float h, float abovehud);
extern void conoutf(const char *s, ...) PRINTFARGS(1, 2);
extern void conoutf(int type, const char *s, ...) PRINTFARGS(2, 3);
extern void resetcomplete();
extern void complete(char *s, int maxlen, const char *cmdprefix);
extern void complete(char *s, int maxlen, const char *cmdprefix, bool names = false);
const char *getkeyname(int code);
extern const char *addreleaseaction(char *s);
extern tagval *addreleaseaction(ident *id, int numargs);
Expand Down
70 changes: 68 additions & 2 deletions source/game/gameclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1110,22 +1110,88 @@ namespace game

VARP(chatmentions, 0, 1, 1);

static const char *lowerstr(const char *s)
{
char *ret = newstring(s);
for(char *p = ret; *p; ++p) *p = (*p == ' ') ? '_' : cubelower(*p);
return ret;
}
static inline void lowername(gameent *pl, char *buf)
{
int i;
for(i = 0; pl->name[i]; ++i) buf[i] = (pl->name[i] == ' ') ? '_' : cubelower(pl->name[i]);
buf[i] = '\0';
}
static bool comparenames(gameent *a, gameent *b)
{
static char lower_a[MAXNAMELEN+1], lower_b[MAXNAMELEN+1];

lowername(a, lower_a);
lowername(b, lower_b);

const int val = strcmp(lower_a, lower_b);
if(val < 0) return true;
if(val > 0) return false;
return a < b;
}
const char *completename(const char *start, int len, const char *last, const char *next)
{
vector<gameent *> sorted_players = players;
sorted_players.sort(comparenames);

const char *start_lower = lowerstr(start);
const char *last_lower = last ? lowerstr(last) : NULL;
const char *next_lower = next ? lowerstr(next) : NULL;

loopv(sorted_players)
{
gameent *pl = sorted_players[i];
char name_lower[MAXNAMELEN+1];
lowername(pl, name_lower);
if(strncmp(name_lower, start_lower, len) == 0 &&
(!last || strcmp(name_lower, last_lower) > 0) && (!next || strcmp(name_lower, next_lower) < 0)
)
{
delete[] start_lower; delete[] last_lower; delete[] next_lower;
return pl->name;
}
}

delete[] start_lower; delete[] last_lower; delete[] next_lower;
return NULL;
}

bool ischatmention(const char* text)
{
if (!chatmentions)
{
return false;
}
const char* mention = strstr(text, self->name);
if (mention && mention > text && *(mention - 1) == '@')

// convert name and text to lowercase and remove spaces from name
static char lowername[MAXNAMELEN+1];
int i;
for(i = 0; self->name[i+1]; ++i)
{
lowername[i] = isspace(self->name[i]) ? '_' : cubelower(self->name[i]);
}
lowername[i] = '\0';
char *lowertext = newstring(text);
for(const char *p = text; *p; ++p) lowertext[p - text] = cubelower(*p);

// check against the lowercased name
const char* mention = strstr(lowertext, lowername);
if (mention && mention > lowertext && *(mention - 1) == '@')
{
// Checking if a chat message mentions our user name.
const char next = *(mention + strlen(self->name));
if (next == '\0' || isspace(next))
{
delete[] lowertext;
return true;
}
}
delete[] lowertext;
return false;
}

Expand Down
1 change: 1 addition & 0 deletions source/shared/igame.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace game
extern const char *getclientmap();
extern const char *getmapinfo();
extern const char *getscreenshotinfo();
extern const char *completename(const char *start, int len, const char *last, const char *next);

extern int numdynents(const int flags = DYN_PLAYER|DYN_AI);
extern int scaletime(int t);
Expand Down