Line data Source code
1 : // console.cpp: the console buffer, its display, and command line control
2 :
3 : #include "../libprimis-headers/cube.h"
4 : #include "../../shared/stream.h"
5 :
6 : #include "console.h"
7 : #include "control.h"
8 : #include "cs.h"
9 : #include "ui.h"
10 : #include "menus.h"
11 :
12 : //input.h needs rendertext's objects
13 : #include "render/rendertext.h"
14 : #include "render/renderttf.h"
15 : #include "input.h"
16 :
17 : #include "world/octaedit.h"
18 :
19 : int commandmillis = -1;
20 :
21 : struct FilesKey
22 : {
23 : const int type;
24 : const std::string dir,
25 : ext;
26 :
27 0 : FilesKey(int type, const std::string &dir, const std::string &ext) : type(type), dir(dir), ext(ext) {}
28 :
29 0 : bool operator==(const FilesKey &y) const
30 : {
31 0 : return type == y.type && dir == y.dir && ext == y.ext;
32 : }
33 : };
34 :
35 : template<>
36 : struct std::hash<FilesKey>
37 : {
38 0 : size_t operator()(const FilesKey &key) const
39 : {
40 0 : size_t h = 5381;
41 0 : for(int i = 0, k; (k = key.dir[i]); i++)
42 : {
43 0 : h = ((h<<5)+h)^k; // bernstein k=33 xor
44 : }
45 0 : return h;
46 : }
47 : };
48 :
49 : class CompletionFinder
50 : {
51 : public:
52 : enum
53 : {
54 : Files_Directory = 0,
55 : Files_List,
56 : };
57 :
58 : void resetcomplete();
59 : void addfilecomplete(const char *command, char *dir, char *ext);
60 : void addlistcomplete(const char *command, char *list);
61 :
62 : void complete(char *s, size_t maxlen, const char *cmdprefix);
63 :
64 : //print to a stream f the listcompletions in the completions filesval
65 : void writecompletions(std::fstream& f) const;
66 :
67 : private:
68 :
69 : struct FilesVal
70 : {
71 : public:
72 : int type;
73 : std::string dir,
74 : ext;
75 : std::vector<char *> files;
76 :
77 : FilesVal(int type, std::string dir, std::string ext);
78 : ~FilesVal();
79 :
80 : void update();
81 :
82 : private:
83 : int millis;
84 : };
85 :
86 : friend std::hash<FilesKey>;
87 :
88 : std::unordered_map<FilesKey, FilesVal *> completefiles;
89 : std::unordered_map<const char *, FilesVal *> completions;
90 :
91 : int completesize = 0;
92 : char *lastcomplete = nullptr;
93 :
94 : void addcomplete(const char *command, int type, char *dir, char *ext);
95 :
96 : char *prependstring(char *d, const char *s, size_t len) const;
97 : };
98 :
99 0 : CompletionFinder::FilesVal::FilesVal(int type, std::string dir, std::string ext) : type(type), dir(dir), ext(ext[0] ? std::string(ext) : ""), millis(-1)
100 : {
101 0 : }
102 :
103 0 : CompletionFinder::FilesVal::~FilesVal()
104 : {
105 0 : for(char* i : files)
106 : {
107 0 : delete[] i;
108 : }
109 0 : }
110 :
111 0 : void CompletionFinder::FilesVal::update()
112 : {
113 0 : if(type!=Files_Directory || millis >= commandmillis)
114 : {
115 0 : return;
116 : }
117 : //first delete old cached file vector
118 0 : for(char* i : files)
119 : {
120 0 : delete[] i;
121 : }
122 : //generate new one
123 0 : listfiles(dir.c_str(), ext.c_str(), files);
124 0 : std::sort(files.begin(), files.end());
125 0 : for(uint i = 0; i < files.size(); i++)
126 : {
127 0 : if(i && !std::strcmp(files[i], files[i-1]))
128 : {
129 0 : delete[] files.at(i);
130 0 : files.erase(files.begin() + i);
131 0 : i--; //we need to make up for the element we destroyed
132 : }
133 : }
134 0 : millis = totalmillis;
135 : }
136 :
137 0 : void CompletionFinder::resetcomplete()
138 : {
139 0 : completesize = 0;
140 0 : }
141 :
142 1 : void CompletionFinder::addfilecomplete(const char *command, char *dir, char *ext)
143 : {
144 1 : addcomplete(command, Files_Directory, dir, ext);
145 1 : }
146 :
147 1 : void CompletionFinder::addlistcomplete(const char *command, char *list)
148 : {
149 1 : addcomplete(command, Files_List, list, nullptr);
150 1 : }
151 :
152 0 : void CompletionFinder::complete(char *s, size_t maxlen, const char *cmdprefix)
153 : {
154 0 : size_t cmdlen = 0;
155 0 : if(cmdprefix)
156 : {
157 0 : cmdlen = std::strlen(cmdprefix);
158 0 : if(std::strncmp(s, cmdprefix, cmdlen))
159 : {
160 0 : prependstring(s, cmdprefix, maxlen);
161 : }
162 : }
163 0 : if(!s[cmdlen])
164 : {
165 0 : return;
166 : }
167 0 : if(!completesize)
168 : {
169 0 : completesize = static_cast<int>(std::strlen(&s[cmdlen]));
170 0 : delete[] lastcomplete;
171 0 : lastcomplete = nullptr;
172 : }
173 0 : FilesVal *f = nullptr;
174 0 : if(completesize)
175 : {
176 0 : char *end = std::strchr(&s[cmdlen], ' ');
177 0 : if(end)
178 : {
179 0 : f = completions[stringslice(&s[cmdlen], end).str];
180 : }
181 : }
182 0 : const char *nextcomplete = nullptr;
183 0 : if(f) // complete using filenames
184 : {
185 0 : int commandsize = std::strchr(&s[cmdlen], ' ')+1-s;
186 0 : f->update();
187 0 : for(const char * i : f->files)
188 : {
189 0 : if(std::strncmp(i, &s[commandsize], completesize+cmdlen-commandsize)==0 &&
190 0 : (!lastcomplete || std::strcmp(i, lastcomplete) > 0) &&
191 0 : (!nextcomplete || std::strcmp(i, nextcomplete) < 0))
192 : {
193 0 : nextcomplete = i;
194 : }
195 : }
196 0 : cmdprefix = s;
197 0 : cmdlen = commandsize;
198 : }
199 : else // complete using command or var (ident) names
200 : {
201 0 : for(auto& [k, id] : idents)
202 : {
203 0 : if(std::strncmp(id.name, &s[cmdlen], completesize)==0 &&
204 0 : (!lastcomplete || std::strcmp(id.name, lastcomplete) > 0) &&
205 0 : (!nextcomplete || std::strcmp(id.name, nextcomplete) < 0))
206 : {
207 0 : nextcomplete = id.name;
208 : }
209 : }
210 : }
211 :
212 0 : delete[] lastcomplete;
213 0 : lastcomplete = nullptr;
214 0 : if(nextcomplete)
215 : {
216 0 : cmdlen = std::min(cmdlen, maxlen-1);
217 0 : if(cmdlen)
218 : {
219 0 : std::memmove(s, cmdprefix, cmdlen);
220 : }
221 0 : copystring(&s[cmdlen], nextcomplete, maxlen-cmdlen);
222 0 : lastcomplete = newstring(nextcomplete);
223 : }
224 : }
225 :
226 : //print to a stream f the listcompletions in the completions filesval
227 0 : void CompletionFinder::writecompletions(std::fstream& f) const
228 : {
229 0 : std::vector<std::string> cmds;
230 0 : for(auto &[k, v] : completions)
231 : {
232 0 : if(v)
233 : {
234 0 : cmds.push_back(k);
235 : }
236 : }
237 0 : std::sort(cmds.begin(), cmds.end());
238 0 : for(std::string &k : cmds)
239 : {
240 0 : auto itr = completions.find(k.c_str());
241 0 : if(itr == completions.end())
242 : {
243 0 : conoutf("could not write completion");
244 0 : return;
245 : }
246 0 : const FilesVal *v = (*itr).second;
247 0 : if(v->type==Files_List)
248 : {
249 0 : if(validateblock(v->dir.c_str()))
250 : {
251 0 : f << "listcomplete " << escapeid(k.c_str()) << " [" << v->dir << "]\n";
252 : }
253 : else
254 : {
255 0 : f << "listcomplete " << escapeid(k.c_str()) << " " << escapestring(v->dir.c_str()) << std::endl;
256 : }
257 : }
258 : else
259 : {
260 0 : f << "complete " << escapeid(k.c_str()) << " " << escapestring(v->dir.c_str()) << " " << escapestring(v->ext.size() ? v->ext.c_str() : "*") << std::endl;
261 : }
262 : }
263 0 : }
264 :
265 2 : void CompletionFinder::addcomplete(const char *command, int type, char *dir, char *ext)
266 : {
267 2 : if(identflags&Idf_Overridden)
268 : {
269 0 : conoutf(Console_Error, "cannot override complete %s", command);
270 2 : return;
271 : }
272 2 : if(!dir[0])
273 : {
274 2 : auto hasfilesitr = completions.find(command);
275 2 : if(hasfilesitr != completions.end())
276 : {
277 0 : (*hasfilesitr).second = nullptr;
278 : }
279 2 : return;
280 : }
281 0 : if(type==Files_Directory)
282 : {
283 0 : int dirlen = static_cast<int>(std::strlen(dir));
284 0 : while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
285 : {
286 0 : dir[--dirlen] = '\0';
287 : }
288 0 : if(ext)
289 : {
290 0 : if(std::strchr(ext, '*'))
291 : {
292 0 : ext[0] = '\0';
293 : }
294 0 : if(!ext[0])
295 : {
296 0 : ext = nullptr;
297 : }
298 : }
299 : }
300 0 : FilesKey key(type, dir ? dir : "", dir ? dir : "");
301 0 : auto itr = completefiles.find(key);
302 0 : if(itr == completefiles.end())
303 : {
304 0 : FilesVal *f = new FilesVal(type, dir ? dir : "", ext ? ext : "");
305 0 : if(type==Files_List)
306 : {
307 0 : explodelist(dir, f->files);
308 : }
309 0 : FilesKey newfile = FilesKey(type, f->dir, f->ext);
310 0 : itr = completefiles.insert(std::pair<FilesKey, FilesVal *>(newfile, f)).first;
311 0 : }
312 0 : auto hasfilesitr = completions.find(std::string(command).c_str());
313 0 : if(hasfilesitr != completions.end())
314 : {
315 0 : (*hasfilesitr).second = (*itr).second;
316 : }
317 : else
318 : {
319 0 : FilesVal *v = (*itr).second;
320 0 : completions[newstring(command)] = v;
321 : }
322 0 : }
323 :
324 0 : char *CompletionFinder::prependstring(char *d, const char *s, size_t len) const
325 : {
326 0 : size_t slen = std::min(std::strlen(s), len);
327 0 : std::memmove(&d[slen], d, std::min(len - slen, std::strlen(d) + 1));
328 0 : std::memcpy(d, s, slen);
329 0 : d[len-1] = 0;
330 0 : return d;
331 : }
332 :
333 : //internally relevant functionality
334 : namespace
335 : {
336 : constexpr int maxconsolelines = 1000; //maximum length of conlines reverse queue
337 :
338 : struct cline
339 : {
340 : char *line; //text contents of the line
341 : int type, //one of the enum values Console_* in headers/consts.h
342 : outtime; //timestamp when the console line was created
343 : };
344 : std::deque<cline> conlines; //global storage of console lines
345 :
346 : string commandbuf;
347 : char *commandaction = nullptr,
348 : *commandprompt = nullptr;
349 : enum CommandFlags
350 : {
351 : CmdFlags_Complete = 1<<0,
352 : CmdFlags_Execute = 1<<1,
353 : };
354 :
355 : int commandflags = 0,
356 : commandpos = -1;
357 :
358 0 : VARFP(maxcon, 10, 200, maxconsolelines,
359 : {
360 : while(static_cast<int>(conlines.size()) > maxcon)
361 : {
362 : delete[] conlines.front().line;
363 : conlines.pop_back();
364 : }
365 : });
366 :
367 : constexpr int constrlen = 512;
368 :
369 : // tab-completion of all idents and base maps
370 :
371 : CompletionFinder cfinder;
372 :
373 698 : void conline(int type, const char *sf) // add a line to the console buffer
374 : {
375 698 : char *buf = static_cast<int>(conlines.size()) >= maxcon ? conlines.back().line : newstring("", constrlen-1);
376 698 : if(static_cast<int>(conlines.size()) >= maxcon)
377 : {
378 401 : conlines.pop_back();
379 : }
380 : cline cl;
381 698 : cl.line = buf;
382 698 : cl.type = type;
383 698 : cl.outtime = totalmillis; // for how long to keep line on screen
384 698 : copystring(cl.line, sf, constrlen);
385 698 : conlines.push_front(cl);
386 698 : }
387 :
388 1 : void fullconsole(int *val, int *numargs, ident *id)
389 : {
390 1 : if(*numargs > 0)
391 : {
392 0 : UI::holdui("fullconsole", *val!=0);
393 : }
394 : else
395 : {
396 1 : int vis = UI::uivisible("fullconsole") ? 1 : 0;
397 1 : if(*numargs < 0)
398 : {
399 0 : intret(vis);
400 : }
401 : else
402 : {
403 1 : printvar(id, vis);
404 : }
405 : }
406 1 : }
407 :
408 1 : void toggleconsole()
409 : {
410 1 : UI::toggleui("fullconsole");
411 1 : }
412 :
413 : VARP(miniconsize, 0, 5, 100); //miniature console font size
414 : VARP(miniconwidth, 0, 40, 100); //miniature console width
415 : VARP(confade, 0, 30, 60); //seconds before fading console
416 : VARP(miniconfade, 0, 30, 60);
417 : HVARP(confilter, 0, 0xFFFFFF, 0xFFFFFF);
418 : HVARP(fullconfilter, 0, 0xFFFFFF, 0xFFFFFF);
419 : HVARP(miniconfilter, 0, 0, 0xFFFFFF);
420 :
421 : int conskip = 0,
422 : miniconskip = 0;
423 :
424 2 : void setconskip(int &skip, int filter, int n)
425 : {
426 2 : int offsetnum = std::abs(n),
427 2 : dir = n < 0 ? -1 : 1;
428 2 : skip = std::clamp(skip, 0, static_cast<int>(conlines.size()-1));
429 2 : while(offsetnum)
430 : {
431 0 : skip += dir;
432 0 : if(!(static_cast<int>(conlines.size()) > skip))
433 : {
434 0 : skip = std::clamp(skip, 0, static_cast<int>(conlines.size()-1));
435 0 : return;
436 : }
437 0 : if(skip < 0)
438 : {
439 0 : skip = 0;
440 0 : break;
441 : }
442 0 : if(conlines[skip].type&filter)
443 : {
444 0 : --offsetnum;
445 : }
446 : }
447 : }
448 :
449 1 : void clearconsole()
450 : {
451 201 : while(conlines.size())
452 : {
453 200 : delete[] conlines.back().line;
454 200 : conlines.pop_back();
455 : }
456 1 : }
457 :
458 0 : float drawconlines(int conskip, int confade, float conwidth, float conheight, float conoff, int filter, float y = 0, int dir = 1)
459 : {
460 0 : int numl = conlines.size(),
461 0 : offsetlines = std::min(conskip, numl);
462 0 : if(confade)
463 : {
464 0 : if(!conskip)
465 : {
466 0 : numl = 0;
467 0 : for(int i = conlines.size(); --i >=0;) //note reverse iteration
468 : {
469 0 : if(totalmillis-conlines[i].outtime < confade*1000)
470 : {
471 0 : numl = i+1;
472 0 : break;
473 : }
474 : }
475 : }
476 : else
477 : {
478 0 : offsetlines--;
479 : }
480 : }
481 :
482 0 : int totalheight = 0;
483 0 : for(int i = 0; i < numl; ++i) //determine visible height
484 : {
485 : // shuffle backwards to fill if necessary
486 0 : int idx = offsetlines+i < numl ? offsetlines+i : --offsetlines;
487 0 : if(!(conlines[idx].type&filter))
488 : {
489 0 : continue;
490 : }
491 0 : char *line = conlines[idx].line;
492 : float width, height;
493 0 : text_boundsf(line, width, height, conwidth);
494 0 : if(totalheight + height > conheight)
495 : {
496 0 : numl = i;
497 0 : if(offsetlines == idx)
498 : {
499 0 : ++offsetlines;
500 : }
501 0 : break;
502 : }
503 0 : totalheight += height;
504 : }
505 0 : if(dir > 0)
506 : {
507 0 : y = conoff;
508 : }
509 0 : for(int i = 0; i < numl; ++i)
510 : {
511 0 : int idx = offsetlines + (dir > 0 ? numl-i-1 : i);
512 0 : if(!(conlines[idx].type&filter))
513 : {
514 0 : continue;
515 : }
516 0 : char *line = conlines[idx].line;
517 : float width, height;
518 0 : text_boundsf(line, width, height, conwidth);
519 0 : if(dir <= 0)
520 : {
521 0 : y -= height;
522 : }
523 : //draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth);
524 0 : ttr.fontsize(50);
525 0 : ttr.renderttf(line, {0xFF, 0xFF, 0xFF, 0}, conoff, y);
526 0 : if(dir > 0)
527 : {
528 0 : y += height;
529 : }
530 : }
531 0 : return y+conoff;
532 : }
533 :
534 : // keymap is defined externally in keymap.cfg
535 :
536 : /*
537 : * defines a mapping for a single key
538 : * multiple keymap objects are aggregated in keyms to create the entire bindings list
539 : */
540 : struct KeyMap
541 : {
542 : enum
543 : {
544 : Action_Default = 0,
545 : Action_Spectator,
546 : Action_Editing,
547 : Action_NumActions
548 : };
549 :
550 : int code; //unique bind code assigned to the key
551 : char *name; //name to use to access this key
552 : char *actions[Action_NumActions]; //array of strings to execute depending on what mode is being used
553 : bool pressed; //whether this key is currently depressed
554 :
555 1 : KeyMap() : code(-1), name(nullptr), pressed(false)
556 : {
557 4 : for(int i = 0; i < Action_NumActions; ++i)
558 : {
559 3 : actions[i] = newstring("");
560 : }
561 1 : }
562 1 : ~KeyMap()
563 : {
564 1 : delete[] name;
565 1 : name = nullptr;
566 4 : for(int i = 0; i < Action_NumActions; ++i)
567 : {
568 3 : delete[] actions[i];
569 3 : actions[i] = nullptr;
570 : }
571 1 : }
572 :
573 : void clear(int type);
574 0 : void clear()
575 : {
576 0 : for(int i = 0; i < Action_NumActions; ++i)
577 : {
578 0 : clear(i);
579 : }
580 0 : }
581 : };
582 :
583 :
584 : KeyMap *keypressed = nullptr;
585 : char *keyaction = nullptr;
586 :
587 0 : void KeyMap::clear(int type)
588 : {
589 0 : char *&binding = actions[type];
590 0 : if(binding[0])
591 : {
592 0 : if(!keypressed || keyaction!=binding)
593 : {
594 0 : delete[] binding;
595 : }
596 0 : binding = newstring("");
597 : }
598 0 : }
599 :
600 : std::map<int, KeyMap> keyms;
601 :
602 1 : void keymap(int *code, char *key)
603 : {
604 1 : if(identflags&Idf_Overridden)
605 : {
606 0 : conoutf(Console_Error, "cannot override keymap %d", *code);
607 0 : return;
608 : }
609 1 : KeyMap &km = keyms[*code];
610 1 : km.code = *code;
611 1 : delete[] km.name;
612 1 : km.name = newstring(key);
613 : }
614 :
615 3 : void searchbinds(char *action, int type)
616 : {
617 3 : std::vector<char> names;
618 3 : for(auto &[k, km] : keyms)
619 : {
620 0 : if(!std::strcmp(km.actions[type], action))
621 : {
622 0 : if(names.size())
623 : {
624 0 : names.push_back(' ');
625 : }
626 0 : for(uint i = 0; i < std::strlen(km.name); ++i)
627 : {
628 0 : names.push_back(km.name[i]);
629 : }
630 : }
631 : }
632 3 : names.push_back('\0');
633 3 : result(names.data());
634 3 : }
635 :
636 6 : KeyMap *findbind(const char *key)
637 : {
638 6 : for(auto &[k, km] : keyms)
639 : {
640 0 : if(!strcasecmp(km.name, key))
641 : {
642 0 : return &km;
643 : }
644 : }
645 6 : return nullptr;
646 : }
647 :
648 3 : void getbind(const char *key, int type)
649 : {
650 3 : KeyMap *km = findbind(key);
651 3 : result(km ? km->actions[type] : "");
652 3 : }
653 :
654 3 : void bindkey(const char *key, const char *action, int state, const char *cmd)
655 : {
656 3 : if(identflags&Idf_Overridden)
657 : {
658 0 : conoutf(Console_Error, "cannot override %s \"%s\"", cmd, key);
659 0 : return;
660 : }
661 3 : KeyMap *km = findbind(key);
662 3 : if(!km)
663 : {
664 3 : conoutf(Console_Error, "unknown key \"%s\"", key);
665 3 : return;
666 : }
667 0 : char *&binding = km->actions[state];
668 0 : if(!keypressed || keyaction!=binding)
669 : {
670 0 : delete[] binding;
671 : }
672 : // trim white-space to make searchbinds more reliable
673 0 : while(iscubespace(*action))
674 : {
675 0 : action++;
676 : }
677 0 : int len = std::strlen(action);
678 0 : while(len>0 && iscubespace(action[len-1]))
679 : {
680 0 : len--;
681 : }
682 0 : binding = newstring(action, len);
683 : }
684 :
685 2 : void inputcommand(char *init, char *action = nullptr, char *prompt = nullptr, char *flags = nullptr) // turns input to the command line on or off
686 : {
687 2 : commandmillis = init ? totalmillis : -1;
688 2 : textinput(commandmillis >= 0, TextInput_Console);
689 2 : keyrepeat(commandmillis >= 0, KeyRepeat_Console);
690 2 : copystring(commandbuf, init ? init : "");
691 :
692 2 : delete[] commandaction;
693 2 : delete[] commandprompt;
694 2 : commandaction = nullptr;
695 2 : commandprompt = nullptr;
696 :
697 2 : commandpos = -1;
698 2 : if(action && action[0])
699 : {
700 0 : commandaction = newstring(action);
701 : }
702 2 : if(prompt && prompt[0])
703 : {
704 0 : commandprompt = newstring(prompt);
705 : }
706 2 : commandflags = 0;
707 2 : if(flags)
708 : {
709 1 : while(*flags)
710 : {
711 0 : switch(*flags++)
712 : {
713 0 : case 'c':
714 : {
715 0 : commandflags |= CmdFlags_Complete;
716 0 : break;
717 : }
718 0 : case 'x':
719 : {
720 0 : commandflags |= CmdFlags_Execute;
721 0 : break;
722 : }
723 0 : case 's':
724 : {
725 0 : commandflags |= CmdFlags_Complete|CmdFlags_Execute;
726 0 : break;
727 : }
728 : }
729 : }
730 : }
731 1 : else if(init)
732 : {
733 1 : commandflags |= CmdFlags_Complete|CmdFlags_Execute;
734 : }
735 2 : }
736 :
737 1 : void saycommand(char *init)
738 : {
739 1 : inputcommand(init);
740 1 : }
741 :
742 0 : void pasteconsole()
743 : {
744 0 : if(!SDL_HasClipboardText())
745 : {
746 0 : return;
747 : }
748 0 : char *cb = SDL_GetClipboardText();
749 0 : if(!cb)
750 : {
751 0 : return;
752 : }
753 0 : size_t cblen = std::strlen(cb),
754 0 : commandlen = std::strlen(commandbuf);
755 0 : if(strlen(commandbuf) + cblen < 260)
756 : {
757 0 : std::memcpy(reinterpret_cast<uchar *>(&commandbuf[commandlen]), cb, cblen);
758 : }
759 0 : commandbuf[commandlen + cblen] = '\0';
760 0 : SDL_free(cb);
761 : }
762 :
763 : struct HLine
764 : {
765 : char *buf, *action, *prompt;
766 : int flags;
767 :
768 0 : HLine() : buf(nullptr), action(nullptr), prompt(nullptr), flags(0) {}
769 0 : ~HLine()
770 : {
771 0 : delete[] buf;
772 0 : delete[] action;
773 0 : delete[] prompt;
774 :
775 0 : buf = nullptr;
776 0 : action = nullptr;
777 0 : prompt = nullptr;
778 0 : }
779 :
780 0 : void restore() const
781 : {
782 0 : copystring(commandbuf, buf);
783 0 : if(commandpos >= static_cast<int>(std::strlen(commandbuf)))
784 : {
785 0 : commandpos = -1;
786 : }
787 :
788 0 : delete[] commandaction;
789 0 : delete[] commandprompt;
790 :
791 0 : commandaction = nullptr;
792 0 : commandprompt = nullptr;
793 :
794 0 : if(action)
795 : {
796 0 : commandaction = newstring(action);
797 : }
798 0 : if(prompt)
799 : {
800 0 : commandprompt = newstring(prompt);
801 : }
802 0 : commandflags = flags;
803 0 : }
804 :
805 0 : bool shouldsave() const
806 : {
807 0 : return std::strcmp(commandbuf, buf) ||
808 0 : (commandaction ? !action || std::strcmp(commandaction, action) : action!=nullptr) ||
809 0 : (commandprompt ? !prompt || std::strcmp(commandprompt, prompt) : prompt!=nullptr) ||
810 0 : commandflags != flags;
811 : }
812 :
813 0 : void save()
814 : {
815 0 : buf = newstring(commandbuf);
816 0 : if(commandaction)
817 : {
818 0 : action = newstring(commandaction);
819 : }
820 0 : if(commandprompt)
821 : {
822 0 : prompt = newstring(commandprompt);
823 : }
824 0 : flags = commandflags;
825 0 : }
826 :
827 0 : void run() const
828 : {
829 0 : if(flags&CmdFlags_Execute && buf[0]=='/')
830 : {
831 0 : execute(buf+1);
832 : }
833 0 : else if(action)
834 : {
835 0 : alias("commandbuf", buf);
836 0 : execute(action);
837 : }
838 : else
839 : {
840 0 : conoutf(Console_Info, "%s", buf);
841 : }
842 0 : }
843 : };
844 : std::vector<HLine *> history;
845 : int histpos = 0;
846 :
847 : VARP(maxhistory, 0, 1000, 10000);
848 :
849 1 : void historycmd(int *n)
850 : {
851 : static bool inhistory = false;
852 1 : if(!inhistory && static_cast<int>(history.size()) > *n)
853 : {
854 0 : inhistory = true;
855 0 : history[history.size()-*n-1]->run();
856 0 : inhistory = false;
857 : }
858 1 : }
859 :
860 : struct releaseaction
861 : {
862 : KeyMap *key;
863 : union
864 : {
865 : char *action;
866 : ident *id;
867 : };
868 : int numargs;
869 : tagval args[3];
870 : };
871 : std::vector<releaseaction> releaseactions;
872 :
873 1 : const char *addreleaseaction(char *s)
874 : {
875 1 : if(!keypressed)
876 : {
877 1 : delete[] s;
878 1 : return nullptr;
879 : }
880 0 : releaseactions.emplace_back();
881 0 : releaseaction &ra = releaseactions.back();
882 0 : ra.key = keypressed;
883 0 : ra.action = s;
884 0 : ra.numargs = -1;
885 0 : return keypressed->name;
886 : }
887 :
888 1 : void onrelease(const char *s)
889 : {
890 1 : addreleaseaction(newstring(s));
891 1 : }
892 :
893 0 : static void execbind(KeyMap &k, bool isdown, int map)
894 : {
895 0 : for(uint i = 0; i < releaseactions.size(); i++)
896 : {
897 0 : releaseaction &ra = releaseactions[i];
898 0 : if(ra.key==&k)
899 : {
900 0 : if(ra.numargs < 0)
901 : {
902 0 : if(!isdown)
903 : {
904 0 : execute(ra.action);
905 : }
906 0 : delete[] ra.action;
907 : }
908 : else
909 : {
910 0 : execute(isdown ? nullptr : ra.id, ra.args, ra.numargs);
911 : }
912 0 : releaseactions.erase(releaseactions.begin() + i);
913 0 : i--;
914 : }
915 : }
916 0 : if(isdown)
917 : {
918 0 : int state = KeyMap::Action_Default;
919 0 : if(!mainmenu)
920 : {
921 0 : if(map == 1)
922 : {
923 0 : state = KeyMap::Action_Editing;
924 : }
925 0 : else if(map == 2)
926 : {
927 0 : state = KeyMap::Action_Spectator;
928 : }
929 : }
930 0 : char *&action = k.actions[state][0] ? k.actions[state] : k.actions[KeyMap::Action_Default];
931 0 : keyaction = action;
932 0 : keypressed = &k;
933 0 : execute(keyaction);
934 0 : keypressed = nullptr;
935 0 : if(keyaction!=action)
936 : {
937 0 : delete[] keyaction;
938 : }
939 : }
940 0 : k.pressed = isdown;
941 0 : }
942 :
943 0 : bool consoleinput(const char *str, int len)
944 : {
945 0 : if(commandmillis < 0)
946 : {
947 0 : return false;
948 : }
949 0 : ::cfinder.resetcomplete();
950 0 : int cmdlen = static_cast<int>(std::strlen(commandbuf)),
951 0 : cmdspace = static_cast<int>(sizeof(commandbuf)) - (cmdlen+1);
952 0 : len = std::min(len, cmdspace);
953 0 : if(commandpos<0)
954 : {
955 0 : std::memcpy(&commandbuf[cmdlen], str, len);
956 : }
957 : else
958 : {
959 0 : std::memmove(&commandbuf[commandpos+len], &commandbuf[commandpos], cmdlen - commandpos);
960 0 : std::memcpy(&commandbuf[commandpos], str, len);
961 0 : commandpos += len;
962 : }
963 0 : commandbuf[cmdlen + len] = '\0';
964 :
965 0 : return true;
966 : }
967 :
968 0 : bool consolekey(int code, bool isdown)
969 : {
970 0 : if(commandmillis < 0)
971 : {
972 0 : return false;
973 : }
974 0 : if(isdown)
975 : {
976 0 : switch(code)
977 : {
978 0 : case SDLK_RETURN:
979 : case SDLK_KP_ENTER:
980 : {
981 0 : break;
982 : }
983 0 : case SDLK_HOME:
984 : {
985 0 : if(std::strlen(commandbuf))
986 : {
987 0 : commandpos = 0;
988 : }
989 0 : break;
990 : }
991 0 : case SDLK_END:
992 : {
993 0 : commandpos = -1;
994 0 : break;
995 : }
996 0 : case SDLK_DELETE:
997 : {
998 0 : size_t len = std::strlen(commandbuf);
999 0 : if(commandpos<0)
1000 : {
1001 0 : break;
1002 : }
1003 0 : std::memmove(&commandbuf[commandpos], &commandbuf[commandpos+1], len - commandpos);
1004 0 : ::cfinder.resetcomplete();
1005 0 : if(commandpos >= static_cast<int>(len-1))
1006 : {
1007 0 : commandpos = -1;
1008 : }
1009 0 : break;
1010 : }
1011 0 : case SDLK_BACKSPACE:
1012 : {
1013 0 : size_t len = std::strlen(commandbuf);
1014 0 : int i = commandpos>=0 ? commandpos : len;
1015 0 : if(i<1)
1016 : {
1017 0 : break;
1018 : }
1019 0 : std::memmove(&commandbuf[i-1], &commandbuf[i], len - i + 1);
1020 0 : ::cfinder.resetcomplete();
1021 0 : if(commandpos>0)
1022 : {
1023 0 : commandpos--;
1024 : }
1025 0 : else if(!commandpos && len<=1)
1026 : {
1027 0 : commandpos = -1;
1028 : }
1029 0 : break;
1030 : }
1031 0 : case SDLK_LEFT:
1032 : {
1033 0 : if(commandpos>0)
1034 : {
1035 0 : commandpos--;
1036 : }
1037 0 : else if(commandpos<0)
1038 : {
1039 0 : commandpos = static_cast<int>(std::strlen(commandbuf))-1;
1040 : }
1041 0 : break;
1042 : }
1043 0 : case SDLK_RIGHT:
1044 : {
1045 0 : if(commandpos>=0 && ++commandpos >= static_cast<int>(std::strlen(commandbuf)))
1046 : {
1047 0 : commandpos = -1;
1048 : }
1049 0 : break;
1050 : }
1051 0 : case SDLK_UP:
1052 : {
1053 0 : if(histpos > static_cast<int>(history.size()))
1054 : {
1055 0 : histpos = history.size();
1056 : }
1057 0 : if(histpos > 0)
1058 : {
1059 0 : history[--histpos]->restore();
1060 : }
1061 0 : break;
1062 : }
1063 0 : case SDLK_DOWN:
1064 : {
1065 0 : if(histpos + 1 < static_cast<int>(history.size()))
1066 : {
1067 0 : history[++histpos]->restore();
1068 : }
1069 0 : break;
1070 : }
1071 0 : case SDLK_TAB:
1072 : {
1073 0 : if(commandflags&CmdFlags_Complete)
1074 : {
1075 0 : ::cfinder.complete(commandbuf, sizeof(commandbuf), commandflags&CmdFlags_Execute ? "/" : nullptr);
1076 0 : if(commandpos>=0 && commandpos >= static_cast<int>(std::strlen(commandbuf)))
1077 : {
1078 0 : commandpos = -1;
1079 : }
1080 : }
1081 0 : break;
1082 : }
1083 0 : case SDLK_v:
1084 : {
1085 0 : if(SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL))//mod keys
1086 : {
1087 0 : pasteconsole();
1088 : }
1089 0 : break;
1090 : }
1091 : }
1092 : }
1093 : else
1094 : {
1095 0 : if(code==SDLK_RETURN || code==SDLK_KP_ENTER)
1096 : {
1097 0 : HLine *h = nullptr;
1098 0 : if(commandbuf[0])
1099 : {
1100 0 : if(history.empty() || history.back()->shouldsave())
1101 : {
1102 0 : if(maxhistory && static_cast<int>(history.size()) >= maxhistory)
1103 : {
1104 0 : for(uint i = 0; i < (history.size()-maxhistory+1); ++i)
1105 : {
1106 0 : delete history[i];
1107 : }
1108 0 : history.erase(history.begin(), history.begin() + history.size()-maxhistory+1);
1109 : }
1110 0 : history.emplace_back(h = new HLine)->save();
1111 : }
1112 : else
1113 : {
1114 0 : h = history.back();
1115 : }
1116 : }
1117 0 : histpos = history.size();
1118 0 : inputcommand(nullptr);
1119 0 : if(h)
1120 : {
1121 0 : h->run();
1122 : }
1123 0 : }
1124 0 : else if(code==SDLK_ESCAPE)
1125 : {
1126 0 : histpos = history.size();
1127 0 : inputcommand(nullptr);
1128 : }
1129 : }
1130 :
1131 0 : return true;
1132 : }
1133 : }
1134 :
1135 : //iengine.h
1136 0 : void clear_console()
1137 : {
1138 0 : keyms.clear();
1139 0 : }
1140 :
1141 : //console.h
1142 0 : void processtextinput(const char *str, int len)
1143 : {
1144 0 : if(!UI::textinput(str, len))
1145 : {
1146 0 : consoleinput(str, len);
1147 : }
1148 0 : }
1149 :
1150 0 : void processkey(int code, bool isdown, int map)
1151 : {
1152 0 : auto itr = keyms.find(code);
1153 0 : if(itr != keyms.end() && (*itr).second.pressed)
1154 : {
1155 0 : execbind((*itr).second, isdown, map); // allow pressed keys to release
1156 : }
1157 0 : else if(!UI::keypress(code, isdown)) // UI key intercept
1158 : {
1159 0 : if(!consolekey(code, isdown))
1160 : {
1161 0 : if(itr != keyms.end())
1162 : {
1163 0 : execbind((*itr).second, isdown, map);
1164 : }
1165 : }
1166 : }
1167 0 : }
1168 :
1169 0 : float rendercommand(float x, float y, float w)
1170 : {
1171 0 : if(commandmillis < 0)
1172 : {
1173 0 : return 0;
1174 : }
1175 : char buf[constrlen];
1176 0 : const char *prompt = commandprompt ? commandprompt : ">";
1177 0 : formatstring(buf, "%s %s", prompt, commandbuf);
1178 : float width, height;
1179 0 : text_boundsf(buf, width, height, w);
1180 0 : y -= height;
1181 0 : ttr.fontsize(50);
1182 0 : ttr.renderttf(buf, {0xFF, 0xFF, 0xFF, 0}, x, y);
1183 : //draw_text(buf, x, y, 0xFF, 0xFF, 0xFF, 0xFF, commandpos>=0 ? commandpos+1 + std::strlen(prompt) : std::strlen(buf), w);
1184 0 : return height;
1185 : }
1186 :
1187 0 : float renderfullconsole(float w, float h)
1188 : {
1189 0 : float conpad = FONTH/2,
1190 0 : conheight = h - 2*conpad,
1191 0 : conwidth = w - 2*conpad;
1192 0 : drawconlines(conskip, 0, conwidth, conheight, conpad, fullconfilter);
1193 0 : return conheight + 2*conpad;
1194 : }
1195 :
1196 0 : float renderconsole(float w, float h, float abovehud)
1197 : {
1198 0 : static VARP(consize, 0, 5, 100); //font size of the console text
1199 0 : float conpad = FONTH/2,
1200 0 : conheight = std::min(static_cast<float>(FONTH*consize), h - 2*conpad),
1201 0 : conwidth = w - 2*conpad,
1202 0 : y = drawconlines(conskip, confade, conwidth, conheight, conpad, confilter);
1203 0 : if(miniconsize && miniconwidth)
1204 : {
1205 0 : drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*conpad))/100, std::min(static_cast<float>(FONTH*miniconsize), abovehud - y), conpad, miniconfilter, abovehud, -1);
1206 : }
1207 0 : return y;
1208 : }
1209 :
1210 698 : void conoutfv(int type, const char *fmt, va_list args)
1211 : {
1212 : static char buf[constrlen];
1213 698 : vformatstring(buf, fmt, args, sizeof(buf));
1214 698 : conline(type, buf);
1215 698 : logoutf("%s", buf);
1216 698 : }
1217 :
1218 575 : void conoutf(const char *fmt, ...)
1219 : {
1220 : va_list args;
1221 575 : va_start(args, fmt);
1222 575 : conoutfv(Console_Info, fmt, args);
1223 575 : va_end(args);
1224 575 : }
1225 :
1226 117 : void conoutf(int type, const char *fmt, ...)
1227 : {
1228 : va_list args;
1229 117 : va_start(args, fmt);
1230 117 : conoutfv(type, fmt, args);
1231 117 : va_end(args);
1232 117 : }
1233 :
1234 0 : const char *getkeyname(int code)
1235 : {
1236 0 : auto itr = keyms.find(code);
1237 0 : return itr != keyms.end() ? (*itr).second.name : nullptr;
1238 : }
1239 :
1240 1 : tagval *addreleaseaction(ident *id, int numargs)
1241 : {
1242 1 : if(!keypressed || numargs > 3)
1243 : {
1244 1 : return nullptr;
1245 : }
1246 0 : releaseactions.emplace_back();
1247 0 : releaseaction &ra = releaseactions.back();
1248 0 : ra.key = keypressed;
1249 0 : ra.id = id;
1250 0 : ra.numargs = numargs;
1251 0 : return ra.args;
1252 : }
1253 :
1254 : //print to a stream f the binds in the binds vector
1255 0 : void writebinds(std::fstream& f)
1256 : {
1257 : static const char * const cmds[3] = { "bind", "specbind", "editbind" };
1258 0 : std::vector<KeyMap *> binds;
1259 0 : for(auto &[k, km] : keyms)
1260 : {
1261 0 : binds.push_back(&km);
1262 : }
1263 0 : std::sort(binds.begin(), binds.end());
1264 0 : for(int j = 0; j < 3; ++j)
1265 : {
1266 0 : for(KeyMap *&km : binds)
1267 : {
1268 0 : if(*(km->actions[j]))
1269 : {
1270 0 : if(validateblock(km->actions[j]))
1271 : {
1272 0 : f << cmds[j] << " " << escapestring(km->name) << " [" << km->actions[j] << "]\n";
1273 : }
1274 : else
1275 : {
1276 0 : f << cmds[j] << " " << escapestring(km->name) << " " << escapestring(km->actions[j]) << std::endl;
1277 : }
1278 : }
1279 : }
1280 : }
1281 0 : }
1282 :
1283 0 : extern void writecompletions(std::fstream& f)
1284 : {
1285 0 : ::cfinder.writecompletions(f);
1286 0 : }
1287 :
1288 1 : void initconsolecmds()
1289 : {
1290 1 : addcommand("fullconsole", reinterpret_cast<identfun>(fullconsole), "iN$", Id_Command);
1291 1 : addcommand("toggleconsole", reinterpret_cast<identfun>(toggleconsole), "", Id_Command);
1292 :
1293 1 : static auto conskipcmd = [] (int *n)
1294 : {
1295 1 : setconskip(conskip, UI::uivisible("fullconsole") ? fullconfilter : confilter, *n);
1296 1 : };
1297 1 : addcommand("conskip", reinterpret_cast<identfun>(+conskipcmd), "i", Id_Command);
1298 :
1299 :
1300 1 : static auto miniconskipcmd = [] (int *n)
1301 : {
1302 1 : setconskip(miniconskip, miniconfilter, *n);
1303 1 : };
1304 1 : addcommand("miniconskip", reinterpret_cast<identfun>(+miniconskipcmd), "i", Id_Command);
1305 :
1306 1 : addcommand("clearconsole", reinterpret_cast<identfun>(clearconsole), "", Id_Command);
1307 1 : addcommand("keymap", reinterpret_cast<identfun>(keymap), "is", Id_Command);
1308 :
1309 1 : static auto bind = [] (char *key, char *action)
1310 : {
1311 1 : bindkey(key, action, KeyMap::Action_Default, "bind");
1312 1 : };
1313 1 : addcommand("bind", reinterpret_cast<identfun>(+bind), "ss", Id_Command);
1314 :
1315 1 : static auto specbind = [] (char *key, char *action)
1316 : {
1317 1 : bindkey(key, action, KeyMap::Action_Spectator, "specbind");
1318 1 : };
1319 1 : addcommand("specbind", reinterpret_cast<identfun>(+specbind), "ss", Id_Command);
1320 :
1321 1 : static auto editbind = [] (char *key, char *action)
1322 : {
1323 1 : bindkey(key, action, KeyMap::Action_Editing, "editbind");
1324 1 : };
1325 1 : addcommand("editbind", reinterpret_cast<identfun>(+editbind), "ss", Id_Command);
1326 :
1327 1 : static auto getbindcmd = [] (char *key)
1328 : {
1329 1 : getbind(key, KeyMap::Action_Default);
1330 1 : };
1331 1 : addcommand("getbind", reinterpret_cast<identfun>(+getbindcmd), "s", Id_Command);
1332 :
1333 1 : static auto getspecbind = [] (char *key)
1334 : {
1335 1 : getbind(key, KeyMap::Action_Spectator);
1336 1 : };
1337 1 : addcommand("getspecbind", reinterpret_cast<identfun>(+getspecbind), "s", Id_Command);
1338 :
1339 1 : static auto geteditbind = [] (char *key)
1340 : {
1341 1 : getbind(key, KeyMap::Action_Editing);
1342 1 : };
1343 1 : addcommand("geteditbind", reinterpret_cast<identfun>(+geteditbind), "s", Id_Command);
1344 :
1345 1 : static auto searchbindscmd = [] (char *action)
1346 : {
1347 1 : searchbinds(action, KeyMap::Action_Default);
1348 1 : };
1349 1 : addcommand("searchbinds", reinterpret_cast<identfun>(+searchbindscmd), "s", Id_Command);
1350 :
1351 1 : static auto searchspecbinds = [] (char *action)
1352 : {
1353 1 : searchbinds(action, KeyMap::Action_Spectator);
1354 1 : };
1355 1 : addcommand("searchspecbinds", reinterpret_cast<identfun>(+searchspecbinds), "s", Id_Command);
1356 :
1357 1 : static auto searcheditbinds = [] (char *action)
1358 : {
1359 1 : searchbinds(action, KeyMap::Action_Editing);
1360 1 : };
1361 1 : addcommand("searcheditbinds", reinterpret_cast<identfun>(+searcheditbinds), "s", Id_Command);
1362 :
1363 1 : static auto clearbinds = [] ()
1364 : {
1365 1 : for(auto &[k, km] : keyms)
1366 : {
1367 0 : km.clear(KeyMap::Action_Default);
1368 : }
1369 1 : };
1370 1 : addcommand("clearbinds", reinterpret_cast<identfun>(+clearbinds), "", Id_Command);
1371 :
1372 1 : static auto clearspecbinds = [] ()
1373 : {
1374 1 : for(auto &[k, km] : keyms)
1375 : {
1376 0 : km.clear(KeyMap::Action_Spectator);
1377 : }
1378 1 : };
1379 1 : addcommand("clearspecbinds", reinterpret_cast<identfun>(+clearspecbinds), "", Id_Command);
1380 :
1381 1 : static auto cleareditbinds = [] ()
1382 : {
1383 1 : for(auto &[k, km] : keyms)
1384 : {
1385 0 : km.clear(KeyMap::Action_Editing);
1386 : }
1387 1 : };
1388 1 : addcommand("cleareditbinds", reinterpret_cast<identfun>(+cleareditbinds), "", Id_Command);
1389 :
1390 1 : static auto clearallbinds = [] ()
1391 : {
1392 1 : for(auto &[k, km] : keyms)
1393 : {
1394 0 : km.clear();
1395 : }
1396 1 : };
1397 1 : addcommand("clearallbinds", reinterpret_cast<identfun>(+clearallbinds), "", Id_Command);
1398 1 : addcommand("inputcommand", reinterpret_cast<identfun>(inputcommand), "ssss", Id_Command);
1399 1 : addcommand("saycommand", reinterpret_cast<identfun>(saycommand), "C", Id_Command);
1400 1 : addcommand("history", reinterpret_cast<identfun>(historycmd), "i", Id_Command);
1401 1 : addcommand("onrelease", reinterpret_cast<identfun>(onrelease), "s", Id_Command);
1402 2 : addcommand("complete", reinterpret_cast<identfun>(+[] (char *command, char *dir, char *ext) {::cfinder.addfilecomplete(command, dir, ext);}), "sss", Id_Command);
1403 2 : addcommand("listcomplete", reinterpret_cast<identfun>(+[] (char *command, char *list) {::cfinder.addlistcomplete(command, list);}), "ss", Id_Command);
1404 1 : }
|