DEADSOFTWARE

added bind command + autoexec.cfg + dfconfig.cfg
[d2df-sdl.git] / src / game / g_console.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_console;
19 interface
21 uses
22 utils; // for SSArray
24 const
25 ACTION_MOVEUP = 1;
26 ACTION_MOVEDOWN = 2;
27 ACTION_MOVELEFT = 3;
28 ACTION_MOVERIGHT = 4;
29 ACTION_SPEED = 5;
30 ACTION_SCORES = 6;
31 ACTION_LOOKDOWN = 7;
32 ACTION_LOOKUP = 8;
33 ACTION_ATTACK = 9;
34 ACTION_ACTIVATE = 10;
35 ACTION_STRAFE = 11;
36 ACTION_WEAPNEXT = 12;
37 ACTION_WEAPPREV = 13;
38 ACTION_WEAP1 = 14;
39 ACTION_WEAP2 = 15;
40 ACTION_WEAP3 = 16;
41 ACTION_WEAP4 = 17;
42 ACTION_WEAP5 = 18;
43 ACTION_WEAP6 = 19;
44 ACTION_WEAP7 = 20;
45 ACTION_WEAP8 = 21;
46 ACTION_WEAP9 = 22;
47 ACTION_WEAP10 = 23;
48 ACTION_WEAP11 = 24;
50 LAST_ACTION = ACTION_WEAP11;
51 MAX_ACTION_WEAP = ACTION_WEAP11 - ACTION_WEAP1 + 1;
53 procedure g_Console_Init ();
54 procedure g_Console_Update ();
55 procedure g_Console_Draw ();
56 procedure g_Console_Switch ();
57 procedure g_Console_Char (C: AnsiChar);
58 procedure g_Console_Control (K: Word);
59 procedure g_Console_Process (L: AnsiString; quiet: Boolean=false);
60 procedure g_Console_Add (L: AnsiString; show: Boolean=false);
61 procedure g_Console_Clear ();
62 function g_Console_CommandBlacklisted (C: AnsiString): Boolean;
63 procedure g_Console_ReadConfig (filename: String);
65 procedure g_Console_ProcessBind (key: Integer; down: Boolean);
67 procedure conwriteln (const s: AnsiString; show: Boolean=false);
68 procedure conwritefln (const s: AnsiString; args: array of const; show: Boolean=false);
70 // <0: no arg; 0/1: true/false
71 function conGetBoolArg (p: SSArray; idx: Integer): Integer;
73 procedure g_Console_Chat_Switch (team: Boolean=false);
75 procedure conRegVar (const conname: AnsiString; pvar: PBoolean; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
76 procedure conRegVar (const conname: AnsiString; pvar: PSingle; amin, amax: Single; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
77 procedure conRegVar (const conname: AnsiString; pvar: PInteger; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
79 // poor man's floating literal parser; i'm sorry, but `StrToFloat()` sux cocks
80 function conParseFloat (var res: Single; const s: AnsiString): Boolean;
83 var
84 gConsoleShow: Boolean = false; // True - êîíñîëü îòêðûòà èëè îòêðûâàåòñÿ
85 gChatShow: Boolean = false;
86 gChatTeam: Boolean = false;
87 gAllowConsoleMessages: Boolean = true;
88 gChatEnter: Boolean = true;
89 gJustChatted: Boolean = false; // ÷òîáû àäìèí â èíòåðå ÷àòÿñü íå ïðîìàòûâàë ñòàòèñòèêó
90 gPlayerAction: Array [0..1, 0..LAST_ACTION] of Boolean; // [player, action]
92 implementation
94 uses
95 g_textures, g_main, e_graphics, e_input, g_game,
96 SysUtils, g_basic, g_options, Math, g_touch,
97 g_menu, g_language, g_net, g_netmsg, e_log, conbuf;
100 type
101 PCommand = ^TCommand;
103 TCmdProc = procedure (p: SSArray);
104 TCmdProcEx = procedure (me: PCommand; p: SSArray);
106 TCommand = record
107 cmd: AnsiString;
108 proc: TCmdProc;
109 procEx: TCmdProcEx;
110 help: AnsiString;
111 hidden: Boolean;
112 ptr: Pointer; // various data
113 msg: AnsiString; // message for var changes
114 cheat: Boolean;
115 end;
117 TAlias = record
118 name: AnsiString;
119 commands: SSArray;
120 end;
123 const
124 Step = 32;
125 Alpha = 25;
126 MsgTime = 144;
127 MaxScriptRecursion = 16;
129 DEBUG_STRING = 'DEBUG MODE';
131 var
132 ID: DWORD;
133 RecursionDepth: Word = 0;
134 RecursionLimitHit: Boolean = False;
135 Cons_Y: SmallInt;
136 Cons_Shown: Boolean; // Ðèñîâàòü ëè êîíñîëü?
137 Line: AnsiString;
138 CPos: Word;
139 //ConsoleHistory: SSArray;
140 CommandHistory: SSArray;
141 Whitelist: SSArray;
142 commands: Array of TCommand = nil;
143 Aliases: Array of TAlias = nil;
144 CmdIndex: Word;
145 conSkipLines: Integer = 0;
146 MsgArray: Array [0..4] of record
147 Msg: AnsiString;
148 Time: Word;
149 end;
150 gInputBinds: Array [0..e_MaxInputKeys - 1] of record
151 cmd: AnsiString
152 end;
153 bindDown, bindProcess: Boolean;
156 // poor man's floating literal parser; i'm sorry, but `StrToFloat()` sux cocks
157 function conParseFloat (var res: Single; const s: AnsiString): Boolean;
158 var
159 pos: Integer = 1;
160 frac: Single = 1;
161 slen: Integer;
162 begin
163 result := false;
164 res := 0;
165 slen := Length(s);
166 while (slen > 0) and (s[slen] <= ' ') do Dec(slen);
167 while (pos <= slen) and (s[pos] <= ' ') do Inc(pos);
168 if (pos > slen) then exit;
169 if (slen-pos = 1) and (s[pos] = '.') then exit; // single dot
170 // integral part
171 while (pos <= slen) do
172 begin
173 if (s[pos] < '0') or (s[pos] > '9') then break;
174 res := res*10+Byte(s[pos])-48;
175 Inc(pos);
176 end;
177 if (pos <= slen) then
178 begin
179 // must be a dot
180 if (s[pos] <> '.') then exit;
181 Inc(pos);
182 while (pos <= slen) do
183 begin
184 if (s[pos] < '0') or (s[pos] > '9') then break;
185 frac := frac/10;
186 res += frac*(Byte(s[pos])-48);
187 Inc(pos);
188 end;
189 end;
190 if (pos <= slen) then exit; // oops
191 result := true;
192 end;
195 // ////////////////////////////////////////////////////////////////////////// //
196 // <0: no arg; 0/1: true/false; 666: toggle
197 function conGetBoolArg (p: SSArray; idx: Integer): Integer;
198 begin
199 if (idx < 0) or (idx > High(p)) then begin result := -1; exit; end;
200 result := 0;
201 if (p[idx] = '1') or (CompareText(p[idx], 'on') = 0) or (CompareText(p[idx], 'true') = 0) or
202 (CompareText(p[idx], 'tan') = 0) or (CompareText(p[idx], 'yes') = 0) then result := 1
203 else if (CompareText(p[idx], 'toggle') = 0) or (CompareText(p[idx], 'switch') = 0) or
204 (CompareText(p[idx], 't') = 0) then result := 666;
205 end;
208 procedure boolVarHandler (me: PCommand; p: SSArray);
209 procedure binaryFlag (var flag: Boolean; msg: AnsiString);
210 begin
211 if (Length(p) > 2) then
212 begin
213 conwritefln('too many arguments to ''%s''', [p[0]]);
214 end
215 else
216 begin
217 case conGetBoolArg(p, 1) of
218 -1: begin end;
219 0: if not me.cheat or conIsCheatsEnabled then flag := false else begin conwriteln('not available'); exit; end;
220 1: if not me.cheat or conIsCheatsEnabled then flag := true else begin conwriteln('not available'); exit; end;
221 666: if not me.cheat or conIsCheatsEnabled then flag := not flag else begin conwriteln('not available'); exit; end;
222 end;
223 if (Length(msg) = 0) then msg := p[0] else msg += ':';
224 if flag then conwritefln('%s tan', [msg]) else conwritefln('%s ona', [msg]);
225 end;
226 end;
227 begin
228 binaryFlag(PBoolean(me.ptr)^, me.msg);
229 end;
232 procedure intVarHandler (me: PCommand; p: SSArray);
233 procedure binaryFlag (var flag: Boolean; msg: AnsiString);
234 begin
235 if (Length(p) > 2) then
236 begin
237 conwritefln('too many arguments to ''%s''', [p[0]]);
238 end
239 else
240 begin
241 case conGetBoolArg(p, 1) of
242 -1: begin end;
243 0: if not me.cheat or conIsCheatsEnabled then flag := false else begin conwriteln('not available'); exit; end;
244 1: if not me.cheat or conIsCheatsEnabled then flag := true else begin conwriteln('not available'); exit; end;
245 666: if not me.cheat or conIsCheatsEnabled then flag := not flag else begin conwriteln('not available'); exit; end;
246 end;
247 if (Length(msg) = 0) then msg := p[0] else msg += ':';
248 if flag then conwritefln('%s tan', [msg]) else conwritefln('%s ona', [msg]);
249 end;
250 end;
251 begin
252 if (Length(p) <> 2) then
253 begin
254 conwritefln('%s %d', [me.cmd, PInteger(me.ptr)^]);
255 end
256 else
257 begin
258 try
259 PInteger(me.ptr)^ := StrToInt(p[1]);
260 except
261 conwritefln('invalid integer value: "%s"', [p[1]]);
262 end;
263 end;
264 end;
267 procedure conRegVar (const conname: AnsiString; pvar: PBoolean; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
268 var
269 f: Integer;
270 cp: PCommand;
271 begin
272 f := Length(commands);
273 SetLength(commands, f+1);
274 cp := @commands[f];
275 cp.cmd := LowerCase(conname);
276 cp.proc := nil;
277 cp.procEx := boolVarHandler;
278 cp.help := ahelp;
279 cp.hidden := ahidden;
280 cp.ptr := pvar;
281 cp.msg := amsg;
282 cp.cheat := acheat;
283 end;
286 procedure conRegVar (const conname: AnsiString; pvar: PInteger; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
287 var
288 f: Integer;
289 cp: PCommand;
290 begin
291 f := Length(commands);
292 SetLength(commands, f+1);
293 cp := @commands[f];
294 cp.cmd := LowerCase(conname);
295 cp.proc := nil;
296 cp.procEx := intVarHandler;
297 cp.help := ahelp;
298 cp.hidden := ahidden;
299 cp.ptr := pvar;
300 cp.msg := amsg;
301 cp.cheat := acheat;
302 end;
305 // ////////////////////////////////////////////////////////////////////////// //
306 type
307 PVarSingle = ^TVarSingle;
308 TVarSingle = record
309 val: PSingle;
310 min, max, def: Single; // default will be starting value
311 end;
314 procedure singleVarHandler (me: PCommand; p: SSArray);
315 var
316 pv: PVarSingle;
317 nv: Single;
318 msg: AnsiString;
319 begin
320 if (Length(p) > 2) then
321 begin
322 conwritefln('too many arguments to ''%s''', [me.cmd]);
323 exit;
324 end;
325 pv := PVarSingle(me.ptr);
326 if (Length(p) = 2) then
327 begin
328 if me.cheat and (not conIsCheatsEnabled) then begin conwriteln('not available'); exit; end;
329 if (CompareText(p[1], 'default') = 0) or (CompareText(p[1], 'def') = 0) or
330 (CompareText(p[1], 'd') = 0) or (CompareText(p[1], 'off') = 0) or
331 (CompareText(p[1], 'ona') = 0) then
332 begin
333 pv.val^ := pv.def;
334 end
335 else
336 begin
337 if not conParseFloat(nv, p[1]) then
338 begin
339 conwritefln('%s: ''%s'' doesn''t look like a floating number', [me.cmd, p[1]]);
340 exit;
341 end;
342 if (nv < pv.min) then nv := pv.min;
343 if (nv > pv.max) then nv := pv.max;
344 pv.val^ := nv;
345 end;
346 end;
347 msg := me.msg;
348 if (Length(msg) = 0) then msg := me.cmd else msg += ':';
349 conwritefln('%s %s', [msg, pv.val^]);
350 end;
353 procedure conRegVar (const conname: AnsiString; pvar: PSingle; amin, amax: Single; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
354 var
355 f: Integer;
356 cp: PCommand;
357 pv: PVarSingle;
358 begin
359 GetMem(pv, sizeof(TVarSingle));
360 pv.val := pvar;
361 pv.min := amin;
362 pv.max := amax;
363 pv.def := pvar^;
364 f := Length(commands);
365 SetLength(commands, f+1);
366 cp := @commands[f];
367 cp.cmd := LowerCase(conname);
368 cp.proc := nil;
369 cp.procEx := singleVarHandler;
370 cp.help := ahelp;
371 cp.hidden := ahidden;
372 cp.ptr := pv;
373 cp.msg := amsg;
374 cp.cheat := acheat;
375 end;
378 // ////////////////////////////////////////////////////////////////////////// //
379 function GetStrACmd(var Str: AnsiString): AnsiString;
380 var
381 a: Integer;
382 begin
383 Result := '';
384 for a := 1 to Length(Str) do
385 if (a = Length(Str)) or (Str[a+1] = ';') then
386 begin
387 Result := Copy(Str, 1, a);
388 Delete(Str, 1, a+1);
389 Str := Trim(Str);
390 Exit;
391 end;
392 end;
394 function ParseAlias(Str: AnsiString): SSArray;
395 begin
396 Result := nil;
398 Str := Trim(Str);
400 if Str = '' then
401 Exit;
403 while Str <> '' do
404 begin
405 SetLength(Result, Length(Result)+1);
406 Result[High(Result)] := GetStrACmd(Str);
407 end;
408 end;
410 procedure ConsoleCommands(p: SSArray);
411 var
412 cmd, s: AnsiString;
413 a, b: Integer;
414 (* F: TextFile; *)
415 begin
416 cmd := LowerCase(p[0]);
417 s := '';
419 if cmd = 'clear' then
420 begin
421 //ConsoleHistory := nil;
422 cbufClear();
423 conSkipLines := 0;
425 for a := 0 to High(MsgArray) do
426 with MsgArray[a] do
427 begin
428 Msg := '';
429 Time := 0;
430 end;
431 end;
433 if cmd = 'clearhistory' then
434 CommandHistory := nil;
436 if cmd = 'showhistory' then
437 if CommandHistory <> nil then
438 begin
439 g_Console_Add('');
440 for a := 0 to High(CommandHistory) do
441 g_Console_Add(' '+CommandHistory[a]);
442 end;
444 if cmd = 'commands' then
445 begin
446 g_Console_Add('');
447 g_Console_Add('commands list:');
448 for a := High(commands) downto 0 do
449 begin
450 if (Length(commands[a].help) > 0) then
451 begin
452 g_Console_Add(' '+commands[a].cmd+' -- '+commands[a].help);
453 end
454 else
455 begin
456 g_Console_Add(' '+commands[a].cmd);
457 end;
458 end;
459 end;
461 if cmd = 'time' then
462 g_Console_Add(TimeToStr(Now), True);
464 if cmd = 'date' then
465 g_Console_Add(DateToStr(Now), True);
467 if cmd = 'echo' then
468 if Length(p) > 1 then
469 begin
470 if p[1] = 'ololo' then
471 gCheats := True
472 else
473 begin
474 s := '';
475 for a := 1 to High(p) do
476 s := s + p[a] + ' ';
477 g_Console_Add(b_Text_Format(s), True);
478 end;
479 end
480 else
481 g_Console_Add('');
483 if cmd = 'dump' then
484 begin
485 (*
486 if ConsoleHistory <> nil then
487 begin
488 if Length(P) > 1 then
489 s := P[1]
490 else
491 s := GameDir+'/console.txt';
493 {$I-}
494 AssignFile(F, s);
495 Rewrite(F);
496 if IOResult <> 0 then
497 begin
498 g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [s]));
499 CloseFile(F);
500 Exit;
501 end;
503 for a := 0 to High(ConsoleHistory) do
504 WriteLn(F, ConsoleHistory[a]);
506 CloseFile(F);
507 g_Console_Add(Format(_lc[I_CONSOLE_DUMPED], [s]));
508 {$I+}
509 end;
510 *)
511 end;
513 if cmd = 'exec' then
514 begin
515 // exec <filename>
516 if Length(p) = 2 then
517 begin
518 s := GameDir + '/' + p[1];
519 g_Console_ReadConfig(s);
520 end
521 else
522 g_Console_Add('exec <script file>');
523 end;
525 if (cmd = 'ver') or (cmd = 'version') then
526 begin
527 conwriteln('Doom 2D: Forever v. ' + GAME_VERSION);
528 conwritefln('Net protocol v. %d', [NET_PROTOCOL_VER]);
529 conwritefln('Build date: %s at %s', [GAME_BUILDDATE, GAME_BUILDTIME]);
530 end;
532 if cmd = 'alias' then
533 begin
534 // alias [alias_name] [commands]
535 if Length(p) > 1 then
536 begin
537 for a := 0 to High(Aliases) do
538 if Aliases[a].name = p[1] then
539 begin
540 if Length(p) > 2 then
541 Aliases[a].commands := ParseAlias(p[2])
542 else
543 for b := 0 to High(Aliases[a].commands) do
544 g_Console_Add(Aliases[a].commands[b]);
545 Exit;
546 end;
547 SetLength(Aliases, Length(Aliases)+1);
548 a := High(Aliases);
549 Aliases[a].name := p[1];
550 if Length(p) > 2 then
551 Aliases[a].commands := ParseAlias(p[2])
552 else
553 for b := 0 to High(Aliases[a].commands) do
554 g_Console_Add(Aliases[a].commands[b]);
555 end else
556 for a := 0 to High(Aliases) do
557 if Aliases[a].commands <> nil then
558 g_Console_Add(Aliases[a].name);
559 end;
561 if cmd = 'call' then
562 begin
563 // call <alias_name>
564 if Length(p) > 1 then
565 begin
566 if Aliases = nil then
567 Exit;
568 for a := 0 to High(Aliases) do
569 if Aliases[a].name = p[1] then
570 begin
571 if Aliases[a].commands <> nil then
572 begin
573 // with this system proper endless loop detection seems either impossible
574 // or very dirty to implement, so let's have this instead
575 // prevents endless loops
576 for b := 0 to High(Aliases[a].commands) do
577 begin
578 Inc(RecursionDepth);
579 RecursionLimitHit := (RecursionDepth > MaxScriptRecursion) or RecursionLimitHit;
580 if not RecursionLimitHit then
581 g_Console_Process(Aliases[a].commands[b], True);
582 Dec(RecursionDepth);
583 end;
584 if (RecursionDepth = 0) and RecursionLimitHit then
585 begin
586 g_Console_Add(Format(_lc[I_CONSOLE_ERROR_CALL], [s]));
587 RecursionLimitHit := False;
588 end;
589 end;
590 Exit;
591 end;
592 end
593 else
594 g_Console_Add('call <alias name>');
595 end;
597 if cmd = '//' then
598 Exit;
599 end;
601 procedure WhitelistCommand(cmd: AnsiString);
602 var
603 a: Integer;
604 begin
605 SetLength(Whitelist, Length(Whitelist)+1);
606 a := High(Whitelist);
607 Whitelist[a] := LowerCase(cmd);
608 end;
610 procedure AddCommand(cmd: AnsiString; proc: TCmdProc; ahelp: AnsiString=''; ahidden: Boolean=false; acheat: Boolean=false);
611 var
612 a: Integer;
613 cp: PCommand;
614 begin
615 SetLength(commands, Length(commands)+1);
616 a := High(commands);
617 cp := @commands[a];
618 cp.cmd := LowerCase(cmd);
619 cp.proc := proc;
620 cp.procEx := nil;
621 cp.help := ahelp;
622 cp.hidden := ahidden;
623 cp.ptr := nil;
624 cp.msg := '';
625 cp.cheat := acheat;
626 end;
629 procedure segfault (p: SSArray);
630 var
631 pp: PByte = nil;
632 begin
633 pp^ := 0;
634 end;
637 procedure BindCommands (p: SSArray);
638 var cmd, key, act: AnsiString; i: Integer;
639 begin
640 cmd := LowerCase(p[0]);
641 case cmd of
642 'bind':
643 // bind <key> <action>
644 if Length(p) = 3 then
645 begin
646 key := LowerCase(p[1]);
647 act := p[2];
648 i := 0;
649 while (i < e_MaxInputKeys) and (key <> LowerCase(e_KeyNames[i])) do inc(i);
650 if i < e_MaxInputKeys then
651 gInputBinds[i].cmd := act
652 end;
653 'bindlist':
654 for i := 0 to e_MaxInputKeys - 1 do
655 if gInputBinds[i].cmd <> '' then
656 g_Console_Add(LowerCase(e_KeyNames[i]) + ' "' + gInputBinds[i].cmd + '"');
657 'unbind':
658 // unbind <key>
659 if Length(p) = 2 then
660 begin
661 key := LowerCase(p[1]);
662 i := 0;
663 while (i < e_MaxInputKeys) and (key <> LowerCase(e_KeyNames[i])) do inc(i);
664 if i < e_MaxInputKeys then
665 gInputBinds[i].cmd := ''
666 end;
667 'unbindall':
668 for i := 0 to e_MaxInputKeys - 1 do
669 gInputBinds[i].cmd := '';
670 'bindkeys':
671 for i := 0 to e_MaxInputKeys - 1 do
672 if e_KeyNames[i] <> '' then
673 g_Console_Add(IntToStr(i) + ': ' + LowerCase(e_KeyNames[i]));
674 end
675 end;
678 procedure KeyActionCommands (p: SSArray);
679 var cmd: AnsiString; val: Boolean; player, action, offset: Integer;
680 begin
681 // syntax: ("+" | "-") ["p" digit "_"] Command
682 cmd := LowerCase(p[0]);
684 if cmd[1] = '+' then
685 val := (not bindProcess) or (bindProcess and bindDown)
686 else if cmd[1] = '-' then
687 val := bindProcess and bindDown
688 else
689 Exit;
691 player := 0;
692 offset := 2;
693 if (Length(cmd) >= 4) and (cmd[2] = 'p') and (cmd[3] >= '1') and (cmd[3] <= '9') and (cmd[4] = '_') then
694 begin
695 player := ord(cmd[3]) - ord('1') - 1;
696 offset := 5;
697 end;
699 case Copy(cmd, offset) of
700 'moveup': action := ACTION_MOVEUP;
701 'movedown': action := ACTION_MOVEDOWN;
702 'moveleft': action := ACTION_MOVELEFT;
703 'moveright': action := ACTION_MOVERIGHT;
704 'speed': action := ACTION_SPEED;
705 'scores': action := ACTION_SCORES;
706 'lookup': action := ACTION_LOOKUP;
707 'lookdown': action := ACTION_LOOKDOWN;
708 'attack': action := ACTION_ATTACK;
709 'activate': action := ACTION_ACTIVATE;
710 'strafe': action := ACTION_STRAFE;
711 'weapnext': action := ACTION_WEAPNEXT;
712 'weapprev': action := ACTION_WEAPPREV;
713 else
714 Exit
715 end;
717 gPlayerAction[player, action] := val;
718 end;
720 procedure ActionCommands (p: SSArray);
721 var cmd: AnsiString; i, player, offset, action: Integer;
722 begin
723 cmd := LowerCase(p[0]);
725 player := 0;
726 offset := 1;
727 if (Length(cmd) >= 3) and (cmd[1] = 'p') and (cmd[2] >= '1') and (cmd[2] <= '9') and (cmd[3] = '_') then
728 begin
729 player := ord(cmd[2]) - ord('1') - 1;
730 offset := 4;
731 end;
733 case Copy(cmd, offset) of
734 'weapnext': action := ACTION_WEAPNEXT;
735 'weapprev': action := ACTION_WEAPPREV;
736 'weapon':
737 if Length(p) = 2 then
738 begin
739 i := StrToInt(p[1]);
740 if (i > 0) and (i <= MAX_ACTION_WEAP) then
741 action := ACTION_WEAP1 + i - 1
742 else
743 Exit
744 end
745 else
746 Exit;
747 end;
749 gPlayerAction[player, action] := bindDown;
750 end;
752 procedure g_Console_Init();
753 const
754 PrefixList: array [0..1] of AnsiString = ('+', '-');
755 PlayerList: array [0..2] of AnsiString = ('', 'p1_', 'p2_');
756 KeyActionList: array [0..12] of AnsiString = ('moveup', 'movedown', 'moveleft', 'moveright', 'speed', 'scores', 'lookup', 'lookdown', 'attack', 'activate', 'strafe', 'weapnext', 'weapprev');
757 ActionList: array [0..2] of AnsiString = ('weapnext', 'weapprev', 'weapon');
758 var
759 a: Integer;
760 s0, s1, s2: AnsiString;
761 begin
762 g_Texture_CreateWAD(ID, GameWAD+':TEXTURES\CONSOLE');
763 Cons_Y := -(gScreenHeight div 2);
764 gConsoleShow := False;
765 gChatShow := False;
766 Cons_Shown := False;
767 CPos := 1;
769 for a := 0 to High(MsgArray) do
770 with MsgArray[a] do
771 begin
772 Msg := '';
773 Time := 0;
774 end;
776 AddCommand('segfault', segfault, 'make segfault');
778 AddCommand('bind', BindCommands);
779 AddCommand('bindlist', BindCommands);
780 AddCommand('unbind', BindCommands);
781 AddCommand('unbindall', BindCommands);
782 AddCommand('bindkeys', BindCommands);
784 for s0 in PrefixList do
785 for s1 in PlayerList do
786 for s2 in KeyActionList do
787 AddCommand(s0 + s1 + s2, KeyActionCommands);
789 for s1 in PlayerList do
790 for s2 in ActionList do
791 AddCommand(s1 + s2, ActionCommands);
793 AddCommand('clear', ConsoleCommands, 'clear console');
794 AddCommand('clearhistory', ConsoleCommands);
795 AddCommand('showhistory', ConsoleCommands);
796 AddCommand('commands', ConsoleCommands);
797 AddCommand('time', ConsoleCommands);
798 AddCommand('date', ConsoleCommands);
799 AddCommand('echo', ConsoleCommands);
800 AddCommand('dump', ConsoleCommands);
801 AddCommand('exec', ConsoleCommands);
802 AddCommand('alias', ConsoleCommands);
803 AddCommand('call', ConsoleCommands);
804 AddCommand('ver', ConsoleCommands);
805 AddCommand('version', ConsoleCommands);
807 AddCommand('d_window', DebugCommands);
808 AddCommand('d_sounds', DebugCommands);
809 AddCommand('d_frames', DebugCommands);
810 AddCommand('d_winmsg', DebugCommands);
811 AddCommand('d_monoff', DebugCommands);
812 AddCommand('d_botoff', DebugCommands);
813 AddCommand('d_monster', DebugCommands);
814 AddCommand('d_health', DebugCommands);
815 AddCommand('d_player', DebugCommands);
816 AddCommand('d_joy', DebugCommands);
817 AddCommand('d_mem', DebugCommands);
819 AddCommand('p1_name', GameCVars);
820 AddCommand('p2_name', GameCVars);
821 AddCommand('p1_color', GameCVars);
822 AddCommand('p2_color', GameCVars);
823 AddCommand('r_showfps', GameCVars);
824 AddCommand('r_showtime', GameCVars);
825 AddCommand('r_showscore', GameCVars);
826 AddCommand('r_showlives', GameCVars);
827 AddCommand('r_showstat', GameCVars);
828 AddCommand('r_showkillmsg', GameCVars);
829 AddCommand('r_showspect', GameCVars);
830 AddCommand('r_showping', GameCVars);
831 AddCommand('g_gamemode', GameCVars);
832 AddCommand('g_friendlyfire', GameCVars);
833 AddCommand('g_weaponstay', GameCVars);
834 AddCommand('g_allow_exit', GameCVars);
835 AddCommand('g_allow_monsters', GameCVars);
836 AddCommand('g_bot_vsmonsters', GameCVars);
837 AddCommand('g_bot_vsplayers', GameCVars);
838 AddCommand('g_scorelimit', GameCVars);
839 AddCommand('g_timelimit', GameCVars);
840 AddCommand('g_maxlives', GameCVars);
841 AddCommand('g_warmuptime', GameCVars);
842 AddCommand('net_interp', GameCVars);
843 AddCommand('net_forceplayerupdate', GameCVars);
844 AddCommand('net_predictself', GameCVars);
845 AddCommand('sv_name', GameCVars);
846 AddCommand('sv_passwd', GameCVars);
847 AddCommand('sv_maxplrs', GameCVars);
848 AddCommand('sv_public', GameCVars);
849 AddCommand('sv_intertime', GameCVars);
851 AddCommand('quit', GameCommands);
852 AddCommand('exit', GameCommands);
853 AddCommand('pause', GameCommands);
854 AddCommand('endgame', GameCommands);
855 AddCommand('restart', GameCommands);
856 AddCommand('addbot', GameCommands);
857 AddCommand('bot_add', GameCommands);
858 AddCommand('bot_addlist', GameCommands);
859 AddCommand('bot_addred', GameCommands);
860 AddCommand('bot_addblue', GameCommands);
861 AddCommand('bot_removeall', GameCommands);
862 AddCommand('chat', GameCommands);
863 AddCommand('teamchat', GameCommands);
864 AddCommand('game', GameCommands);
865 AddCommand('host', GameCommands);
866 AddCommand('map', GameCommands);
867 AddCommand('nextmap', GameCommands);
868 AddCommand('endmap', GameCommands);
869 AddCommand('goodbye', GameCommands);
870 AddCommand('suicide', GameCommands);
871 AddCommand('spectate', GameCommands);
872 AddCommand('ready', GameCommands);
873 AddCommand('kick', GameCommands);
874 AddCommand('kick_id', GameCommands);
875 AddCommand('ban', GameCommands);
876 AddCommand('permban', GameCommands);
877 AddCommand('ban_id', GameCommands);
878 AddCommand('permban_id', GameCommands);
879 AddCommand('unban', GameCommands);
880 AddCommand('connect', GameCommands);
881 AddCommand('disconnect', GameCommands);
882 AddCommand('reconnect', GameCommands);
883 AddCommand('say', GameCommands);
884 AddCommand('tell', GameCommands);
885 AddCommand('overtime', GameCommands);
886 AddCommand('rcon_password', GameCommands);
887 AddCommand('rcon', GameCommands);
888 AddCommand('callvote', GameCommands);
889 AddCommand('vote', GameCommands);
890 AddCommand('clientlist', GameCommands);
891 AddCommand('event', GameCommands);
893 AddCommand('god', GameCheats);
894 AddCommand('notarget', GameCheats);
895 AddCommand('give', GameCheats); // "exit" too ;-)
896 AddCommand('open', GameCheats);
897 AddCommand('fly', GameCheats);
898 AddCommand('noclip', GameCheats);
899 AddCommand('speedy', GameCheats);
900 AddCommand('jumpy', GameCheats);
901 AddCommand('noreload', GameCheats);
902 AddCommand('aimline', GameCheats);
903 AddCommand('automap', GameCheats);
905 WhitelistCommand('say');
906 WhitelistCommand('tell');
907 WhitelistCommand('overtime');
908 WhitelistCommand('ready');
909 WhitelistCommand('map');
910 WhitelistCommand('nextmap');
911 WhitelistCommand('endmap');
912 WhitelistCommand('restart');
913 WhitelistCommand('kick');
914 WhitelistCommand('ban');
916 WhitelistCommand('addbot');
917 WhitelistCommand('bot_add');
918 WhitelistCommand('bot_addred');
919 WhitelistCommand('bot_addblue');
920 WhitelistCommand('bot_removeall');
922 WhitelistCommand('g_gamemode');
923 WhitelistCommand('g_friendlyfire');
924 WhitelistCommand('g_weaponstay');
925 WhitelistCommand('g_allow_exit');
926 WhitelistCommand('g_allow_monsters');
927 WhitelistCommand('g_scorelimit');
928 WhitelistCommand('g_timelimit');
930 g_Console_Add(Format(_lc[I_CONSOLE_WELCOME], [GAME_VERSION]));
931 g_Console_Add('');
933 g_Console_ReadConfig(GameDir + '/dfconfig.cfg');
934 g_Console_ReadConfig(GameDir + '/autoexec.cfg');
935 end;
937 procedure g_Console_Update();
938 var
939 a, b: Integer;
940 begin
941 if Cons_Shown then
942 begin
943 // Â ïðîöåññå îòêðûòèÿ:
944 if gConsoleShow and (Cons_Y < 0) then
945 begin
946 Cons_Y := Cons_Y+Step;
947 end;
949 // Â ïðîöåññå çàêðûòèÿ:
950 if (not gConsoleShow) and
951 (Cons_Y > -(gScreenHeight div 2)) then
952 Cons_Y := Cons_Y-Step;
954 // Îêîí÷àòåëüíî îòêðûëàñü:
955 if Cons_Y > 0 then
956 Cons_Y := 0;
958 // Îêîí÷àòåëüíî çàêðûëàñü:
959 if Cons_Y <= (-(gScreenHeight div 2)) then
960 begin
961 Cons_Y := -(gScreenHeight div 2);
962 Cons_Shown := False;
963 end;
964 end;
966 a := 0;
967 while a <= High(MsgArray) do
968 begin
969 if MsgArray[a].Time > 0 then
970 begin
971 if MsgArray[a].Time = 1 then
972 begin
973 if a < High(MsgArray) then
974 begin
975 for b := a to High(MsgArray)-1 do
976 MsgArray[b] := MsgArray[b+1];
978 MsgArray[High(MsgArray)].Time := 0;
980 a := a - 1;
981 end;
982 end
983 else
984 Dec(MsgArray[a].Time);
985 end;
987 a := a + 1;
988 end;
989 end;
992 procedure drawConsoleText ();
993 var
994 CWidth, CHeight: Byte;
995 ty: Integer;
996 sp, ep: LongWord;
997 skip: Integer;
999 procedure putLine (sp, ep: LongWord);
1000 var
1001 p: LongWord;
1002 wdt, cw: Integer;
1003 begin
1004 p := sp;
1005 wdt := 0;
1006 while p <> ep do
1007 begin
1008 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
1009 if wdt+cw > gScreenWidth-8 then break;
1010 //e_TextureFontPrintChar(X, Y: Integer; Ch: Char; FontID: DWORD; Shadow: Boolean = False);
1011 Inc(wdt, cw);
1012 cbufNext(p);
1013 end;
1014 if p <> ep then putLine(p, ep); // do rest of the line first
1015 // now print our part
1016 if skip = 0 then
1017 begin
1018 ep := p;
1019 p := sp;
1020 wdt := 2;
1021 while p <> ep do
1022 begin
1023 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
1024 e_TextureFontPrintCharEx(wdt, ty, cbufAt(p), gStdFont);
1025 Inc(wdt, cw);
1026 cbufNext(p);
1027 end;
1028 Dec(ty, CHeight);
1029 end
1030 else
1031 begin
1032 Dec(skip);
1033 end;
1034 end;
1036 begin
1037 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1038 ty := (gScreenHeight div 2)-4-2*CHeight-Abs(Cons_Y);
1039 skip := conSkipLines;
1040 cbufLastLine(sp, ep);
1041 repeat
1042 putLine(sp, ep);
1043 if ty+CHeight <= 0 then break;
1044 until not cbufLineUp(sp, ep);
1045 end;
1047 procedure g_Console_Draw();
1048 var
1049 CWidth, CHeight: Byte;
1050 mfW, mfH: Word;
1051 a, b: Integer;
1052 begin
1053 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1055 for a := 0 to High(MsgArray) do
1056 if MsgArray[a].Time > 0 then
1057 e_TextureFontPrintFmt(0, CHeight*a, MsgArray[a].Msg,
1058 gStdFont, True);
1060 if not Cons_Shown then
1061 begin
1062 if gChatShow then
1063 begin
1064 if gChatTeam then
1065 begin
1066 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say team> ' + Line,
1067 gStdFont, 255, 255, 255, 1, True);
1068 e_TextureFontPrintEx((CPos + 9)*CWidth, gScreenHeight - CHeight - 1, '_',
1069 gStdFont, 255, 255, 255, 1, True);
1070 end
1071 else
1072 begin
1073 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say> ' + Line,
1074 gStdFont, 255, 255, 255, 1, True);
1075 e_TextureFontPrintEx((CPos + 4)*CWidth, gScreenHeight - CHeight - 1, '_',
1076 gStdFont, 255, 255, 255, 1, True);
1077 end;
1078 end;
1079 Exit;
1080 end;
1082 if gDebugMode then
1083 begin
1084 e_CharFont_GetSize(gMenuFont, DEBUG_STRING, mfW, mfH);
1085 a := (gScreenWidth - 2*mfW) div 2;
1086 b := Cons_Y + ((gScreenHeight div 2) - 2*mfH) div 2;
1087 e_CharFont_PrintEx(gMenuFont, a div 2, b div 2, DEBUG_STRING,
1088 _RGB(128, 0, 0), 2.0);
1089 end;
1091 e_DrawSize(ID, 0, Cons_Y, Alpha, False, False, gScreenWidth, gScreenHeight div 2);
1092 e_TextureFontPrint(0, Cons_Y+(gScreenHeight div 2)-CHeight-4, '> '+Line, gStdFont);
1094 drawConsoleText();
1095 (*
1096 if ConsoleHistory <> nil then
1097 begin
1098 b := 0;
1099 if CHeight > 0 then
1100 if Length(ConsoleHistory) > ((gScreenHeight div 2) div CHeight)-1 then
1101 b := Length(ConsoleHistory)-((gScreenHeight div 2) div CHeight)+1;
1103 b := Max(b-Offset, 0);
1104 d := Max(High(ConsoleHistory)-Offset, 0);
1106 c := 2;
1107 for a := d downto b do
1108 begin
1109 e_TextureFontPrintFmt(0, (gScreenHeight div 2)-4-c*CHeight-Abs(Cons_Y), ConsoleHistory[a],
1110 gStdFont, True);
1111 c := c + 1;
1112 end;
1113 end;
1114 *)
1116 e_TextureFontPrint((CPos+1)*CWidth, Cons_Y+(gScreenHeight div 2)-21, '_', gStdFont);
1117 end;
1119 procedure g_Console_Switch();
1120 begin
1121 if gChatShow then Exit;
1122 gConsoleShow := not gConsoleShow;
1123 Cons_Shown := True;
1124 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1125 end;
1127 procedure g_Console_Chat_Switch(Team: Boolean = False);
1128 begin
1129 if gConsoleShow then Exit;
1130 if not g_Game_IsNet then Exit;
1131 gChatShow := not gChatShow;
1132 gChatTeam := Team;
1133 if gChatShow then
1134 gChatEnter := False;
1135 Line := '';
1136 CPos := 1;
1137 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1138 end;
1140 procedure g_Console_Char(C: AnsiChar);
1141 begin
1142 if gChatShow and (not gChatEnter) then
1143 Exit;
1144 Insert(C, Line, CPos);
1145 CPos := CPos + 1;
1146 end;
1149 var
1150 tcomplist: array of AnsiString = nil;
1151 tcompidx: array of Integer = nil;
1153 procedure Complete ();
1154 var
1155 i, c: Integer;
1156 tused: Integer;
1157 ll, lpfx, cmd: AnsiString;
1158 begin
1159 if (Length(Line) = 0) then
1160 begin
1161 g_Console_Add('');
1162 for i := 0 to High(commands) do
1163 begin
1164 // hidden commands are hidden when cheats aren't enabled
1165 if commands[i].hidden and not conIsCheatsEnabled then continue;
1166 if (Length(commands[i].help) > 0) then
1167 begin
1168 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1169 end
1170 else
1171 begin
1172 g_Console_Add(' '+commands[i].cmd);
1173 end;
1174 end;
1175 exit;
1176 end;
1178 ll := LowerCase(Line);
1179 lpfx := '';
1181 if (Length(ll) > 1) and (ll[Length(ll)] = ' ') then
1182 begin
1183 ll := Copy(ll, 0, Length(ll)-1);
1184 for i := 0 to High(commands) do
1185 begin
1186 // hidden commands are hidden when cheats aren't enabled
1187 if commands[i].hidden and not conIsCheatsEnabled then continue;
1188 if (commands[i].cmd = ll) then
1189 begin
1190 if (Length(commands[i].help) > 0) then
1191 begin
1192 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1193 end;
1194 end;
1195 end;
1196 exit;
1197 end;
1199 // build completion list
1200 tused := 0;
1201 for i := 0 to High(commands) do
1202 begin
1203 // hidden commands are hidden when cheats aren't enabled
1204 if commands[i].hidden and not conIsCheatsEnabled then continue;
1205 cmd := commands[i].cmd;
1206 if (Length(cmd) >= Length(ll)) and (ll = Copy(cmd, 0, Length(ll))) then
1207 begin
1208 if (tused = Length(tcomplist)) then
1209 begin
1210 SetLength(tcomplist, Length(tcomplist)+128);
1211 SetLength(tcompidx, Length(tcompidx)+128);
1212 end;
1213 tcomplist[tused] := cmd;
1214 tcompidx[tused] := i;
1215 Inc(tused);
1216 if (Length(cmd) > Length(lpfx)) then lpfx := cmd;
1217 end;
1218 end;
1220 // get longest prefix
1221 for i := 0 to tused-1 do
1222 begin
1223 cmd := tcomplist[i];
1224 for c := 1 to Length(lpfx) do
1225 begin
1226 if (c > Length(cmd)) then break;
1227 if (cmd[c] <> lpfx[c]) then begin lpfx := Copy(lpfx, 0, c-1); break; end;
1228 end;
1229 end;
1231 if (tused = 0) then exit;
1233 if (tused = 1) then
1234 begin
1235 Line := tcomplist[0]+' ';
1236 CPos := Length(Line)+1;
1237 end
1238 else
1239 begin
1240 // has longest prefix?
1241 if (Length(lpfx) > Length(ll)) then
1242 begin
1243 Line := lpfx;
1244 CPos:= Length(Line)+1;
1245 end
1246 else
1247 begin
1248 g_Console_Add('');
1249 for i := 0 to tused-1 do
1250 begin
1251 if (Length(commands[tcompidx[i]].help) > 0) then
1252 begin
1253 g_Console_Add(' '+tcomplist[i]+' -- '+commands[tcompidx[i]].help);
1254 end
1255 else
1256 begin
1257 g_Console_Add(' '+tcomplist[i]);
1258 end;
1259 end;
1260 end;
1261 end;
1262 end;
1265 procedure g_Console_Control(K: Word);
1266 begin
1267 case K of
1268 IK_BACKSPACE:
1269 if (Length(Line) > 0) and (CPos > 1) then
1270 begin
1271 Delete(Line, CPos-1, 1);
1272 CPos := CPos-1;
1273 end;
1274 IK_DELETE:
1275 if (Length(Line) > 0) and (CPos <= Length(Line)) then
1276 Delete(Line, CPos, 1);
1277 IK_LEFT, IK_KPLEFT, VK_LEFT:
1278 if CPos > 1 then
1279 CPos := CPos - 1;
1280 IK_RIGHT, IK_KPRIGHT, VK_RIGHT:
1281 if CPos <= Length(Line) then
1282 CPos := CPos + 1;
1283 IK_RETURN, IK_KPRETURN, VK_OPEN, VK_FIRE:
1284 begin
1285 if Cons_Shown then
1286 g_Console_Process(Line)
1287 else
1288 if gChatShow then
1289 begin
1290 if (Length(Line) > 0) and g_Game_IsNet then
1291 begin
1292 if gChatTeam then
1293 begin
1294 if g_Game_IsClient then
1295 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_TEAM)
1296 else
1297 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1298 NET_CHAT_TEAM, gPlayer1Settings.Team);
1299 end
1300 else
1301 begin
1302 if g_Game_IsClient then
1303 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_PLAYER)
1304 else
1305 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1306 NET_CHAT_PLAYER);
1307 end;
1308 end;
1310 Line := '';
1311 CPos := 1;
1312 gChatShow := False;
1313 gJustChatted := True;
1314 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1315 end;
1316 end;
1317 IK_TAB:
1318 if not gChatShow then
1319 Complete();
1320 IK_DOWN, IK_KPDOWN, VK_DOWN:
1321 if not gChatShow then
1322 if (CommandHistory <> nil) and
1323 (CmdIndex < Length(CommandHistory)) then
1324 begin
1325 if CmdIndex < Length(CommandHistory)-1 then
1326 CmdIndex := CmdIndex + 1;
1327 Line := CommandHistory[CmdIndex];
1328 CPos := Length(Line) + 1;
1329 end;
1330 IK_UP, IK_KPUP, VK_UP:
1331 if not gChatShow then
1332 if (CommandHistory <> nil) and
1333 (CmdIndex <= Length(CommandHistory)) then
1334 begin
1335 if CmdIndex > 0 then
1336 CmdIndex := CmdIndex - 1;
1337 Line := CommandHistory[CmdIndex];
1338 Cpos := Length(Line) + 1;
1339 end;
1340 IK_PAGEUP, IK_KPPAGEUP, VK_PREV: // PgUp
1341 if not gChatShow then Inc(conSkipLines);
1342 IK_PAGEDN, IK_KPPAGEDN, VK_NEXT: // PgDown
1343 if not gChatShow and (conSkipLines > 0) then Dec(conSkipLines);
1344 IK_HOME, IK_KPHOME:
1345 CPos := 1;
1346 IK_END, IK_KPEND:
1347 CPos := Length(Line) + 1;
1348 end;
1349 end;
1351 function GetStr(var Str: AnsiString): AnsiString;
1352 var
1353 a, b: Integer;
1354 begin
1355 Result := '';
1356 if Str[1] = '"' then
1357 begin
1358 for b := 1 to Length(Str) do
1359 if (b = Length(Str)) or (Str[b+1] = '"') then
1360 begin
1361 Result := Copy(Str, 2, b-1);
1362 Delete(Str, 1, b+1);
1363 Str := Trim(Str);
1364 Exit;
1365 end;
1366 end;
1368 for a := 1 to Length(Str) do
1369 if (a = Length(Str)) or (Str[a+1] = ' ') then
1370 begin
1371 Result := Copy(Str, 1, a);
1372 Delete(Str, 1, a+1);
1373 Str := Trim(Str);
1374 Exit;
1375 end;
1376 end;
1378 function ParseString(Str: AnsiString): SSArray;
1379 begin
1380 Result := nil;
1382 Str := Trim(Str);
1384 if Str = '' then
1385 Exit;
1387 while Str <> '' do
1388 begin
1389 SetLength(Result, Length(Result)+1);
1390 Result[High(Result)] := GetStr(Str);
1391 end;
1392 end;
1394 procedure g_Console_Add (L: AnsiString; show: Boolean=false);
1396 procedure conmsg (s: AnsiString);
1397 var
1398 a: Integer;
1399 begin
1400 if length(s) = 0 then exit;
1401 for a := 0 to High(MsgArray) do
1402 begin
1403 with MsgArray[a] do
1404 begin
1405 if Time = 0 then
1406 begin
1407 Msg := s;
1408 Time := MsgTime;
1409 exit;
1410 end;
1411 end;
1412 end;
1413 for a := 0 to High(MsgArray)-1 do MsgArray[a] := MsgArray[a+1];
1414 with MsgArray[High(MsgArray)] do
1415 begin
1416 Msg := L;
1417 Time := MsgTime;
1418 end;
1419 end;
1421 var
1422 f: Integer;
1423 begin
1424 // put it to console
1425 cbufPut(L);
1426 if (length(L) = 0) or ((L[length(L)] <> #10) and (L[length(L)] <> #13)) then cbufPut(#10);
1428 // now show 'em out of console too
1429 show := show and gAllowConsoleMessages;
1430 if show and gShowMessages then
1431 begin
1432 // Âûâîä ñòðîê ñ ïåðåíîñàìè ïî î÷åðåäè
1433 while length(L) > 0 do
1434 begin
1435 f := Pos(#10, L);
1436 if f <= 0 then f := length(L)+1;
1437 conmsg(Copy(L, 1, f-1));
1438 Delete(L, 1, f);
1439 end;
1440 end;
1442 //SetLength(ConsoleHistory, Length(ConsoleHistory)+1);
1443 //ConsoleHistory[High(ConsoleHistory)] := L;
1445 (*
1446 {$IFDEF HEADLESS}
1447 e_WriteLog('CON: ' + L, MSG_NOTIFY);
1448 {$ENDIF}
1449 *)
1450 end;
1453 var
1454 consolewriterLastWasEOL: Boolean = false;
1456 procedure consolewriter (constref buf; len: SizeUInt);
1457 var
1458 b: PByte;
1459 begin
1460 if (len < 1) then exit;
1461 b := PByte(@buf);
1462 consolewriterLastWasEOL := (b[len-1] = 13) or (b[len-1] = 10);
1463 while (len > 0) do
1464 begin
1465 if (b[0] <> 13) and (b[0] <> 10) then
1466 begin
1467 cbufPut(AnsiChar(b[0]));
1468 end
1469 else
1470 begin
1471 if (len > 1) and (b[0] = 13) then begin len -= 1; b += 1; end;
1472 cbufPut(#10);
1473 end;
1474 len -= 1;
1475 b += 1;
1476 end;
1477 end;
1480 // returns formatted string if `writerCB` is `nil`, empty string otherwise
1481 //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
1482 //TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
1483 procedure conwriteln (const s: AnsiString; show: Boolean=false);
1484 begin
1485 g_Console_Add(s, show);
1486 end;
1489 procedure conwritefln (const s: AnsiString; args: array of const; show: Boolean=false);
1490 begin
1491 if show then
1492 begin
1493 g_Console_Add(formatstrf(s, args), true);
1494 end
1495 else
1496 begin
1497 consolewriterLastWasEOL := false;
1498 formatstrf(s, args, consolewriter);
1499 if not consolewriterLastWasEOL then cbufPut(#10);
1500 end;
1501 end;
1504 procedure g_Console_Clear();
1505 begin
1506 //ConsoleHistory := nil;
1507 cbufClear();
1508 conSkipLines := 0;
1509 end;
1511 procedure AddToHistory(L: AnsiString);
1512 var
1513 len: Integer;
1514 begin
1515 len := Length(CommandHistory);
1517 if (len = 0) or
1518 (LowerCase(CommandHistory[len-1]) <> LowerCase(L)) then
1519 begin
1520 SetLength(CommandHistory, len+1);
1521 CommandHistory[len] := L;
1522 end;
1524 CmdIndex := Length(CommandHistory);
1525 end;
1527 function g_Console_CommandBlacklisted(C: AnsiString): Boolean;
1528 var
1529 Arr: SSArray;
1530 i: Integer;
1531 begin
1532 Result := True;
1534 Arr := nil;
1536 if Trim(C) = '' then
1537 Exit;
1539 Arr := ParseString(C);
1540 if Arr = nil then
1541 Exit;
1543 for i := 0 to High(Whitelist) do
1544 if Whitelist[i] = LowerCase(Arr[0]) then
1545 Result := False;
1546 end;
1548 procedure g_Console_Process(L: AnsiString; quiet: Boolean = False);
1549 var
1550 Arr: SSArray;
1551 i: Integer;
1552 begin
1553 Arr := nil;
1555 if Trim(L) = '' then
1556 Exit;
1558 conSkipLines := 0; // "unscroll"
1560 if L = 'goobers' then
1561 begin
1562 Line := '';
1563 CPos := 1;
1564 gCheats := true;
1565 g_Console_Add('Your memory serves you well.');
1566 exit;
1567 end;
1569 if not quiet then
1570 begin
1571 g_Console_Add('> '+L);
1572 Line := '';
1573 CPos := 1;
1574 end;
1576 Arr := ParseString(L);
1577 if Arr = nil then
1578 Exit;
1580 if commands = nil then
1581 Exit;
1583 if not quiet then
1584 AddToHistory(L);
1586 for i := 0 to High(commands) do
1587 begin
1588 if commands[i].cmd = LowerCase(Arr[0]) then
1589 begin
1590 if assigned(commands[i].procEx) then
1591 begin
1592 commands[i].procEx(@commands[i], Arr);
1593 exit;
1594 end;
1595 if assigned(commands[i].proc) then
1596 begin
1597 commands[i].proc(Arr);
1598 exit;
1599 end;
1600 end;
1601 end;
1603 g_Console_Add(Format(_lc[I_CONSOLE_UNKNOWN], [Arr[0]]));
1604 end;
1607 procedure g_Console_ProcessBind (key: Integer; down: Boolean);
1608 begin
1609 if (key >= 0) and (key < e_MaxInputKeys) and (gInputBinds[key].cmd <> '') then
1610 begin
1611 bindDown := down;
1612 bindProcess := True;
1613 g_Console_Process(gInputBinds[key].cmd, True);
1614 bindProcess := False;
1615 end
1616 end;
1619 procedure g_Console_ReadConfig (filename: String);
1620 var f: TextFile; s: AnsiString; i, len: Integer;
1621 begin
1622 if FileExists(filename) then
1623 begin
1624 AssignFile(f, filename);
1625 Reset(f);
1626 while not EOF(f) do
1627 begin
1628 ReadLn(f, s);
1629 len := Length(s);
1630 if len > 0 then
1631 begin
1632 i := 1;
1633 (* skip spaces *)
1634 while (i <= len) and (s[i] <= ' ') do inc(i);
1635 (* skip comments *)
1636 if (i <= len) and ((s[i] <> '#') and ((i + 1 > len) or (s[i] <> '/') or (s[i + 1] <> '/'))) then
1637 g_Console_Process(s)
1638 end
1639 end;
1640 CloseFile(f)
1641 end
1642 end;
1645 end.