DEADSOFTWARE

used spaceless key names
[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_JUMP = 0;
26 ACTION_MOVELEFT = 1;
27 ACTION_MOVERIGHT = 2;
28 ACTION_LOOKDOWN = 3;
29 ACTION_LOOKUP = 4;
30 ACTION_ATTACK = 5;
31 ACTION_SCORES = 6;
32 ACTION_ACTIVATE = 7;
33 ACTION_STRAFE = 8;
34 ACTION_WEAPNEXT = 9;
35 ACTION_WEAPPREV = 10;
37 FIRST_ACTION = ACTION_JUMP;
38 LAST_ACTION = ACTION_WEAPPREV;
40 procedure g_Console_Init ();
41 procedure g_Console_Update ();
42 procedure g_Console_Draw ();
43 procedure g_Console_Switch ();
44 procedure g_Console_Char (C: AnsiChar);
45 procedure g_Console_Control (K: Word);
46 procedure g_Console_Process (L: AnsiString; quiet: Boolean=false);
47 procedure g_Console_Add (L: AnsiString; show: Boolean=false);
48 procedure g_Console_Clear ();
49 function g_Console_CommandBlacklisted (C: AnsiString): Boolean;
50 procedure g_Console_ReadConfig (filename: String);
51 procedure g_Console_WriteConfig (filename: String);
53 function g_Console_Interactive: Boolean;
54 function g_Console_Action (action: Integer): Boolean;
55 function g_Console_FindBind (n: Integer; cmd: AnsiString): Integer;
56 procedure g_Console_BindKey (key: Integer; cmd: AnsiString);
57 procedure g_Console_ProcessBind (key: Integer; down: Boolean);
58 procedure g_Console_ResetBinds;
60 procedure conwriteln (const s: AnsiString; show: Boolean=false);
61 procedure conwritefln (const s: AnsiString; args: array of const; show: Boolean=false);
63 // <0: no arg; 0/1: true/false
64 function conGetBoolArg (p: SSArray; idx: Integer): Integer;
66 procedure g_Console_Chat_Switch (team: Boolean=false);
68 procedure conRegVar (const conname: AnsiString; pvar: PBoolean; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
69 procedure conRegVar (const conname: AnsiString; pvar: PSingle; amin, amax: Single; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
70 procedure conRegVar (const conname: AnsiString; pvar: PInteger; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
72 // poor man's floating literal parser; i'm sorry, but `StrToFloat()` sux cocks
73 function conParseFloat (var res: Single; const s: AnsiString): Boolean;
76 var
77 gConsoleShow: Boolean = false; // True - êîíñîëü îòêðûòà èëè îòêðûâàåòñÿ
78 gChatShow: Boolean = false;
79 gChatTeam: Boolean = false;
80 gAllowConsoleMessages: Boolean = true;
81 gJustChatted: Boolean = false; // ÷òîáû àäìèí â èíòåðå ÷àòÿñü íå ïðîìàòûâàë ñòàòèñòèêó
82 gPlayerAction, gDefaultAction: Array [0..1, 0..LAST_ACTION] of Boolean; // [player, action]
84 implementation
86 uses
87 g_textures, g_main, e_graphics, e_input, g_game,
88 SysUtils, g_basic, g_options, Math, g_touch,
89 g_menu, g_language, g_net, g_netmsg, e_log, conbuf;
92 type
93 PCommand = ^TCommand;
95 TCmdProc = procedure (p: SSArray);
96 TCmdProcEx = procedure (me: PCommand; p: SSArray);
98 TCommand = record
99 cmd: AnsiString;
100 proc: TCmdProc;
101 procEx: TCmdProcEx;
102 help: AnsiString;
103 hidden: Boolean;
104 ptr: Pointer; // various data
105 msg: AnsiString; // message for var changes
106 cheat: Boolean;
107 action: Integer; // >= 0 for action commands
108 player: Integer; // used for action commands
109 end;
111 TAlias = record
112 name: AnsiString;
113 commands: SSArray;
114 end;
117 const
118 Step = 32;
119 Alpha = 25;
120 MsgTime = 144;
121 MaxScriptRecursion = 16;
123 DEBUG_STRING = 'DEBUG MODE';
125 var
126 ID: DWORD;
127 RecursionDepth: Word = 0;
128 RecursionLimitHit: Boolean = False;
129 Cons_Y: SmallInt;
130 Cons_Shown: Boolean; // Ðèñîâàòü ëè êîíñîëü?
131 Line: AnsiString;
132 CPos: Word;
133 //ConsoleHistory: SSArray;
134 CommandHistory: SSArray;
135 Whitelist: SSArray;
136 commands: Array of TCommand = nil;
137 Aliases: Array of TAlias = nil;
138 CmdIndex: Word;
139 conSkipLines: Integer = 0;
140 MsgArray: Array [0..4] of record
141 Msg: AnsiString;
142 Time: Word;
143 end;
145 bindDown, bindProcess: Boolean;
146 gInputBinds: Array [0..e_MaxInputKeys - 1] of record
147 commands: SSArray;
148 end;
151 // poor man's floating literal parser; i'm sorry, but `StrToFloat()` sux cocks
152 function conParseFloat (var res: Single; const s: AnsiString): Boolean;
153 var
154 pos: Integer = 1;
155 frac: Single = 1;
156 slen: Integer;
157 begin
158 result := false;
159 res := 0;
160 slen := Length(s);
161 while (slen > 0) and (s[slen] <= ' ') do Dec(slen);
162 while (pos <= slen) and (s[pos] <= ' ') do Inc(pos);
163 if (pos > slen) then exit;
164 if (slen-pos = 1) and (s[pos] = '.') then exit; // single dot
165 // integral part
166 while (pos <= slen) do
167 begin
168 if (s[pos] < '0') or (s[pos] > '9') then break;
169 res := res*10+Byte(s[pos])-48;
170 Inc(pos);
171 end;
172 if (pos <= slen) then
173 begin
174 // must be a dot
175 if (s[pos] <> '.') then exit;
176 Inc(pos);
177 while (pos <= slen) do
178 begin
179 if (s[pos] < '0') or (s[pos] > '9') then break;
180 frac := frac/10;
181 res += frac*(Byte(s[pos])-48);
182 Inc(pos);
183 end;
184 end;
185 if (pos <= slen) then exit; // oops
186 result := true;
187 end;
190 // ////////////////////////////////////////////////////////////////////////// //
191 // <0: no arg; 0/1: true/false; 666: toggle
192 function conGetBoolArg (p: SSArray; idx: Integer): Integer;
193 begin
194 if (idx < 0) or (idx > High(p)) then begin result := -1; exit; end;
195 result := 0;
196 if (p[idx] = '1') or (CompareText(p[idx], 'on') = 0) or (CompareText(p[idx], 'true') = 0) or
197 (CompareText(p[idx], 'tan') = 0) or (CompareText(p[idx], 'yes') = 0) then result := 1
198 else if (CompareText(p[idx], 'toggle') = 0) or (CompareText(p[idx], 'switch') = 0) or
199 (CompareText(p[idx], 't') = 0) then result := 666;
200 end;
203 procedure boolVarHandler (me: PCommand; p: SSArray);
204 procedure binaryFlag (var flag: Boolean; msg: AnsiString);
205 begin
206 if (Length(p) > 2) then
207 begin
208 conwritefln('too many arguments to ''%s''', [p[0]]);
209 end
210 else
211 begin
212 case conGetBoolArg(p, 1) of
213 -1: begin end;
214 0: if not me.cheat or conIsCheatsEnabled then flag := false else begin conwriteln('not available'); exit; end;
215 1: if not me.cheat or conIsCheatsEnabled then flag := true else begin conwriteln('not available'); exit; end;
216 666: if not me.cheat or conIsCheatsEnabled then flag := not flag else begin conwriteln('not available'); exit; end;
217 end;
218 if (Length(msg) = 0) then msg := p[0] else msg += ':';
219 if flag then conwritefln('%s tan', [msg]) else conwritefln('%s ona', [msg]);
220 end;
221 end;
222 begin
223 binaryFlag(PBoolean(me.ptr)^, me.msg);
224 end;
227 procedure intVarHandler (me: PCommand; p: SSArray);
228 procedure binaryFlag (var flag: Boolean; msg: AnsiString);
229 begin
230 if (Length(p) > 2) then
231 begin
232 conwritefln('too many arguments to ''%s''', [p[0]]);
233 end
234 else
235 begin
236 case conGetBoolArg(p, 1) of
237 -1: begin end;
238 0: if not me.cheat or conIsCheatsEnabled then flag := false else begin conwriteln('not available'); exit; end;
239 1: if not me.cheat or conIsCheatsEnabled then flag := true else begin conwriteln('not available'); exit; end;
240 666: if not me.cheat or conIsCheatsEnabled then flag := not flag else begin conwriteln('not available'); exit; end;
241 end;
242 if (Length(msg) = 0) then msg := p[0] else msg += ':';
243 if flag then conwritefln('%s tan', [msg]) else conwritefln('%s ona', [msg]);
244 end;
245 end;
246 begin
247 if (Length(p) <> 2) then
248 begin
249 conwritefln('%s %d', [me.cmd, PInteger(me.ptr)^]);
250 end
251 else
252 begin
253 try
254 PInteger(me.ptr)^ := StrToInt(p[1]);
255 except
256 conwritefln('invalid integer value: "%s"', [p[1]]);
257 end;
258 end;
259 end;
262 procedure conRegVar (const conname: AnsiString; pvar: PBoolean; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
263 var
264 f: Integer;
265 cp: PCommand;
266 begin
267 f := Length(commands);
268 SetLength(commands, f+1);
269 cp := @commands[f];
270 cp.cmd := LowerCase(conname);
271 cp.proc := nil;
272 cp.procEx := boolVarHandler;
273 cp.help := ahelp;
274 cp.hidden := ahidden;
275 cp.ptr := pvar;
276 cp.msg := amsg;
277 cp.cheat := acheat;
278 cp.action := -1;
279 cp.player := -1;
280 end;
283 procedure conRegVar (const conname: AnsiString; pvar: PInteger; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
284 var
285 f: Integer;
286 cp: PCommand;
287 begin
288 f := Length(commands);
289 SetLength(commands, f+1);
290 cp := @commands[f];
291 cp.cmd := LowerCase(conname);
292 cp.proc := nil;
293 cp.procEx := intVarHandler;
294 cp.help := ahelp;
295 cp.hidden := ahidden;
296 cp.ptr := pvar;
297 cp.msg := amsg;
298 cp.cheat := acheat;
299 cp.action := -1;
300 cp.player := -1;
301 end;
304 // ////////////////////////////////////////////////////////////////////////// //
305 type
306 PVarSingle = ^TVarSingle;
307 TVarSingle = record
308 val: PSingle;
309 min, max, def: Single; // default will be starting value
310 end;
313 procedure singleVarHandler (me: PCommand; p: SSArray);
314 var
315 pv: PVarSingle;
316 nv: Single;
317 msg: AnsiString;
318 begin
319 if (Length(p) > 2) then
320 begin
321 conwritefln('too many arguments to ''%s''', [me.cmd]);
322 exit;
323 end;
324 pv := PVarSingle(me.ptr);
325 if (Length(p) = 2) then
326 begin
327 if me.cheat and (not conIsCheatsEnabled) then begin conwriteln('not available'); exit; end;
328 if (CompareText(p[1], 'default') = 0) or (CompareText(p[1], 'def') = 0) or
329 (CompareText(p[1], 'd') = 0) or (CompareText(p[1], 'off') = 0) or
330 (CompareText(p[1], 'ona') = 0) then
331 begin
332 pv.val^ := pv.def;
333 end
334 else
335 begin
336 if not conParseFloat(nv, p[1]) then
337 begin
338 conwritefln('%s: ''%s'' doesn''t look like a floating number', [me.cmd, p[1]]);
339 exit;
340 end;
341 if (nv < pv.min) then nv := pv.min;
342 if (nv > pv.max) then nv := pv.max;
343 pv.val^ := nv;
344 end;
345 end;
346 msg := me.msg;
347 if (Length(msg) = 0) then msg := me.cmd else msg += ':';
348 conwritefln('%s %s', [msg, pv.val^]);
349 end;
352 procedure conRegVar (const conname: AnsiString; pvar: PSingle; amin, amax: Single; const ahelp: AnsiString; const amsg: AnsiString; acheat: Boolean=false; ahidden: Boolean=false); overload;
353 var
354 f: Integer;
355 cp: PCommand;
356 pv: PVarSingle;
357 begin
358 GetMem(pv, sizeof(TVarSingle));
359 pv.val := pvar;
360 pv.min := amin;
361 pv.max := amax;
362 pv.def := pvar^;
363 f := Length(commands);
364 SetLength(commands, f+1);
365 cp := @commands[f];
366 cp.cmd := LowerCase(conname);
367 cp.proc := nil;
368 cp.procEx := singleVarHandler;
369 cp.help := ahelp;
370 cp.hidden := ahidden;
371 cp.ptr := pv;
372 cp.msg := amsg;
373 cp.cheat := acheat;
374 cp.action := -1;
375 cp.player := -1;
376 end;
379 // ////////////////////////////////////////////////////////////////////////// //
380 function GetStrACmd(var Str: AnsiString): AnsiString;
381 var
382 a: Integer;
383 begin
384 Result := '';
385 for a := 1 to Length(Str) do
386 if (a = Length(Str)) or (Str[a+1] = ';') then
387 begin
388 Result := Copy(Str, 1, a);
389 Delete(Str, 1, a+1);
390 Str := Trim(Str);
391 Exit;
392 end;
393 end;
395 function ParseAlias(Str: AnsiString): SSArray;
396 begin
397 Result := nil;
399 Str := Trim(Str);
401 if Str = '' then
402 Exit;
404 while Str <> '' do
405 begin
406 SetLength(Result, Length(Result)+1);
407 Result[High(Result)] := GetStrACmd(Str);
408 end;
409 end;
411 procedure ConsoleCommands(p: SSArray);
412 var
413 cmd, s: AnsiString;
414 a, b: Integer;
415 (* F: TextFile; *)
416 begin
417 cmd := LowerCase(p[0]);
418 s := '';
420 if cmd = 'clear' then
421 begin
422 //ConsoleHistory := nil;
423 cbufClear();
424 conSkipLines := 0;
426 for a := 0 to High(MsgArray) do
427 with MsgArray[a] do
428 begin
429 Msg := '';
430 Time := 0;
431 end;
432 end;
434 if cmd = 'clearhistory' then
435 CommandHistory := nil;
437 if cmd = 'showhistory' then
438 if CommandHistory <> nil then
439 begin
440 g_Console_Add('');
441 for a := 0 to High(CommandHistory) do
442 g_Console_Add(' '+CommandHistory[a]);
443 end;
445 if cmd = 'commands' then
446 begin
447 g_Console_Add('');
448 g_Console_Add('commands list:');
449 for a := High(commands) downto 0 do
450 begin
451 if (Length(commands[a].help) > 0) then
452 begin
453 g_Console_Add(' '+commands[a].cmd+' -- '+commands[a].help);
454 end
455 else
456 begin
457 g_Console_Add(' '+commands[a].cmd);
458 end;
459 end;
460 end;
462 if cmd = 'time' then
463 g_Console_Add(TimeToStr(Now), True);
465 if cmd = 'date' then
466 g_Console_Add(DateToStr(Now), True);
468 if cmd = 'echo' then
469 if Length(p) > 1 then
470 begin
471 if p[1] = 'ololo' then
472 gCheats := True
473 else
474 begin
475 s := '';
476 for a := 1 to High(p) do
477 s := s + p[a] + ' ';
478 g_Console_Add(b_Text_Format(s), True);
479 end;
480 end
481 else
482 g_Console_Add('');
484 if cmd = 'dump' then
485 begin
486 (*
487 if ConsoleHistory <> nil then
488 begin
489 if Length(P) > 1 then
490 s := P[1]
491 else
492 s := GameDir+'/console.txt';
494 {$I-}
495 AssignFile(F, s);
496 Rewrite(F);
497 if IOResult <> 0 then
498 begin
499 g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [s]));
500 CloseFile(F);
501 Exit;
502 end;
504 for a := 0 to High(ConsoleHistory) do
505 WriteLn(F, ConsoleHistory[a]);
507 CloseFile(F);
508 g_Console_Add(Format(_lc[I_CONSOLE_DUMPED], [s]));
509 {$I+}
510 end;
511 *)
512 end;
514 if cmd = 'exec' then
515 begin
516 // exec <filename>
517 if Length(p) = 2 then
518 g_Console_ReadConfig(GameDir + '/' + p[1])
519 else
520 g_Console_Add('exec <script file>');
521 end;
523 if cmd = 'writeconfig' then
524 begin
525 // writeconfig <filename>
526 if Length(p) = 2 then
527 g_Console_WriteConfig(GameDir + '/' + p[1])
528 else
529 g_Console_Add('writeconfig <file>');
530 end;
532 if (cmd = 'ver') or (cmd = 'version') then
533 begin
534 conwriteln('Doom 2D: Forever v. ' + GAME_VERSION);
535 conwritefln('Net protocol v. %d', [NET_PROTOCOL_VER]);
536 conwritefln('Build date: %s at %s', [GAME_BUILDDATE, GAME_BUILDTIME]);
537 end;
539 if cmd = 'alias' then
540 begin
541 // alias [alias_name] [commands]
542 if Length(p) > 1 then
543 begin
544 for a := 0 to High(Aliases) do
545 if Aliases[a].name = p[1] then
546 begin
547 if Length(p) > 2 then
548 Aliases[a].commands := ParseAlias(p[2])
549 else
550 for b := 0 to High(Aliases[a].commands) do
551 g_Console_Add(Aliases[a].commands[b]);
552 Exit;
553 end;
554 SetLength(Aliases, Length(Aliases)+1);
555 a := High(Aliases);
556 Aliases[a].name := p[1];
557 if Length(p) > 2 then
558 Aliases[a].commands := ParseAlias(p[2])
559 else
560 for b := 0 to High(Aliases[a].commands) do
561 g_Console_Add(Aliases[a].commands[b]);
562 end else
563 for a := 0 to High(Aliases) do
564 if Aliases[a].commands <> nil then
565 g_Console_Add(Aliases[a].name);
566 end;
568 if cmd = 'call' then
569 begin
570 // call <alias_name>
571 if Length(p) > 1 then
572 begin
573 if Aliases = nil then
574 Exit;
575 for a := 0 to High(Aliases) do
576 if Aliases[a].name = p[1] then
577 begin
578 if Aliases[a].commands <> nil then
579 begin
580 // with this system proper endless loop detection seems either impossible
581 // or very dirty to implement, so let's have this instead
582 // prevents endless loops
583 for b := 0 to High(Aliases[a].commands) do
584 begin
585 Inc(RecursionDepth);
586 RecursionLimitHit := (RecursionDepth > MaxScriptRecursion) or RecursionLimitHit;
587 if not RecursionLimitHit then
588 g_Console_Process(Aliases[a].commands[b], True);
589 Dec(RecursionDepth);
590 end;
591 if (RecursionDepth = 0) and RecursionLimitHit then
592 begin
593 g_Console_Add(Format(_lc[I_CONSOLE_ERROR_CALL], [s]));
594 RecursionLimitHit := False;
595 end;
596 end;
597 Exit;
598 end;
599 end
600 else
601 g_Console_Add('call <alias name>');
602 end;
603 end;
605 procedure WhitelistCommand(cmd: AnsiString);
606 var
607 a: Integer;
608 begin
609 SetLength(Whitelist, Length(Whitelist)+1);
610 a := High(Whitelist);
611 Whitelist[a] := LowerCase(cmd);
612 end;
614 procedure segfault (p: SSArray);
615 var
616 pp: PByte = nil;
617 begin
618 pp^ := 0;
619 end;
621 procedure BindCommands (p: SSArray);
622 var cmd, key, act: AnsiString; i, j: Integer;
623 begin
624 cmd := LowerCase(p[0]);
625 case cmd of
626 'bind':
627 // bind <key> <action>
628 if Length(p) = 3 then
629 begin
630 key := LowerCase(p[1]);
631 i := 0;
632 while (i < e_MaxInputKeys) and (key <> LowerCase(e_KeyNames[i])) do inc(i);
633 if i < e_MaxInputKeys then
634 g_Console_BindKey(i, p[2])
635 end;
636 'bindlist':
637 for i := 0 to e_MaxInputKeys - 1 do
638 begin
639 if gInputBinds[i].commands <> nil then
640 begin
641 act := gInputBinds[i].commands[0];
642 for j := 1 to High(gInputBinds[i].commands) do
643 act := act + ' ;' + gInputBinds[i].commands[j];
644 g_Console_Add(e_KeyNames[i] + ' "' + act + '"')
645 end
646 end;
647 'unbind':
648 // unbind <key>
649 if Length(p) = 2 then
650 begin
651 key := LowerCase(p[1]);
652 i := 0;
653 while (i < e_MaxInputKeys) and (key <> LowerCase(e_KeyNames[i])) do inc(i);
654 if i < e_MaxInputKeys then
655 g_Console_BindKey(i, '')
656 end;
657 'unbindall':
658 for i := 0 to e_MaxInputKeys - 1 do
659 if gInputBinds[i].commands <> nil then
660 gInputBinds[i].commands := nil;
661 end
662 end;
664 procedure AddCommand(cmd: AnsiString; proc: TCmdProc; ahelp: AnsiString=''; ahidden: Boolean=false; acheat: Boolean=false);
665 var
666 a: Integer;
667 cp: PCommand;
668 begin
669 SetLength(commands, Length(commands)+1);
670 a := High(commands);
671 cp := @commands[a];
672 cp.cmd := LowerCase(cmd);
673 cp.proc := proc;
674 cp.procEx := nil;
675 cp.help := ahelp;
676 cp.hidden := ahidden;
677 cp.ptr := nil;
678 cp.msg := '';
679 cp.cheat := acheat;
680 cp.action := -1;
681 cp.player := -1;
682 end;
684 procedure AddAction (cmd: AnsiString; action: Integer; help: AnsiString = ''; hidden: Boolean = False; cheat: Boolean = False);
685 const
686 PrefixList: array [0..1] of AnsiString = ('+', '-');
687 PlayerList: array [0..1] of Integer = (1, 2);
688 var
689 s: AnsiString;
690 i: Integer;
692 procedure NewAction (cmd: AnsiString; player: Integer);
693 var cp: PCommand;
694 begin
695 SetLength(commands, Length(commands) + 1);
696 cp := @commands[High(commands)];
697 cp.cmd := LowerCase(cmd);
698 cp.proc := nil;
699 cp.procEx := nil;
700 cp.help := help;
701 cp.hidden := hidden;
702 cp.ptr := nil;
703 cp.msg := '';
704 cp.cheat := cheat;
705 cp.action := action;
706 cp.player := player;
707 end;
709 begin
710 ASSERT(action >= FIRST_ACTION);
711 ASSERT(action <= LAST_ACTION);
712 for s in PrefixList do
713 begin
714 NewAction(s + cmd, 0);
715 for i in PlayerList do
716 NewAction(s + 'p' + IntToStr(i) + '_' + cmd, i - 1)
717 end
718 end;
720 procedure g_Console_Init();
721 var
722 a: Integer;
723 begin
724 g_Texture_CreateWAD(ID, GameWAD+':TEXTURES\CONSOLE');
725 Cons_Y := -(gScreenHeight div 2);
726 gConsoleShow := False;
727 gChatShow := False;
728 Cons_Shown := False;
729 CPos := 1;
731 for a := 0 to High(MsgArray) do
732 with MsgArray[a] do
733 begin
734 Msg := '';
735 Time := 0;
736 end;
738 AddCommand('segfault', segfault, 'make segfault');
740 AddCommand('bind', BindCommands);
741 AddCommand('bindlist', BindCommands);
742 AddCommand('unbind', BindCommands);
743 AddCommand('unbindall', BindCommands);
744 AddCommand('bindkeys', BindCommands);
746 AddCommand('clear', ConsoleCommands, 'clear console');
747 AddCommand('clearhistory', ConsoleCommands);
748 AddCommand('showhistory', ConsoleCommands);
749 AddCommand('commands', ConsoleCommands);
750 AddCommand('time', ConsoleCommands);
751 AddCommand('date', ConsoleCommands);
752 AddCommand('echo', ConsoleCommands);
753 AddCommand('dump', ConsoleCommands);
754 AddCommand('exec', ConsoleCommands);
755 AddCommand('writeconfig', ConsoleCommands);
756 AddCommand('alias', ConsoleCommands);
757 AddCommand('call', ConsoleCommands);
758 AddCommand('ver', ConsoleCommands);
759 AddCommand('version', ConsoleCommands);
761 AddCommand('d_window', DebugCommands);
762 AddCommand('d_sounds', DebugCommands);
763 AddCommand('d_frames', DebugCommands);
764 AddCommand('d_winmsg', DebugCommands);
765 AddCommand('d_monoff', DebugCommands);
766 AddCommand('d_botoff', DebugCommands);
767 AddCommand('d_monster', DebugCommands);
768 AddCommand('d_health', DebugCommands);
769 AddCommand('d_player', DebugCommands);
770 AddCommand('d_joy', DebugCommands);
771 AddCommand('d_mem', DebugCommands);
773 AddCommand('p1_name', GameCVars);
774 AddCommand('p2_name', GameCVars);
775 AddCommand('p1_color', GameCVars);
776 AddCommand('p2_color', GameCVars);
777 AddCommand('r_showtime', GameCVars);
778 AddCommand('r_showscore', GameCVars);
779 AddCommand('r_showlives', GameCVars);
780 AddCommand('r_showstat', GameCVars);
781 AddCommand('r_showkillmsg', GameCVars);
782 AddCommand('r_showspect', GameCVars);
783 AddCommand('r_showping', GameCVars);
784 AddCommand('g_gamemode', GameCVars);
785 AddCommand('g_friendlyfire', GameCVars);
786 AddCommand('g_weaponstay', GameCVars);
787 AddCommand('g_allow_exit', GameCVars);
788 AddCommand('g_allow_monsters', GameCVars);
789 AddCommand('g_bot_vsmonsters', GameCVars);
790 AddCommand('g_bot_vsplayers', GameCVars);
791 AddCommand('g_scorelimit', GameCVars);
792 AddCommand('g_timelimit', GameCVars);
793 AddCommand('g_maxlives', GameCVars);
794 AddCommand('g_warmuptime', GameCVars);
795 AddCommand('net_interp', GameCVars);
796 AddCommand('net_forceplayerupdate', GameCVars);
797 AddCommand('net_predictself', GameCVars);
798 AddCommand('sv_name', GameCVars);
799 AddCommand('sv_passwd', GameCVars);
800 AddCommand('sv_maxplrs', GameCVars);
801 AddCommand('sv_public', GameCVars);
802 AddCommand('sv_intertime', GameCVars);
804 AddCommand('quit', GameCommands);
805 AddCommand('exit', GameCommands);
806 AddCommand('pause', GameCommands);
807 AddCommand('endgame', GameCommands);
808 AddCommand('restart', GameCommands);
809 AddCommand('addbot', GameCommands);
810 AddCommand('bot_add', GameCommands);
811 AddCommand('bot_addlist', GameCommands);
812 AddCommand('bot_addred', GameCommands);
813 AddCommand('bot_addblue', GameCommands);
814 AddCommand('bot_removeall', GameCommands);
815 AddCommand('chat', GameCommands);
816 AddCommand('teamchat', GameCommands);
817 AddCommand('game', GameCommands);
818 AddCommand('host', GameCommands);
819 AddCommand('map', GameCommands);
820 AddCommand('nextmap', GameCommands);
821 AddCommand('endmap', GameCommands);
822 AddCommand('goodbye', GameCommands);
823 AddCommand('suicide', GameCommands);
824 AddCommand('spectate', GameCommands);
825 AddCommand('ready', GameCommands);
826 AddCommand('kick', GameCommands);
827 AddCommand('kick_id', GameCommands);
828 AddCommand('ban', GameCommands);
829 AddCommand('permban', GameCommands);
830 AddCommand('ban_id', GameCommands);
831 AddCommand('permban_id', GameCommands);
832 AddCommand('unban', GameCommands);
833 AddCommand('connect', GameCommands);
834 AddCommand('disconnect', GameCommands);
835 AddCommand('reconnect', GameCommands);
836 AddCommand('say', GameCommands);
837 AddCommand('tell', GameCommands);
838 AddCommand('overtime', GameCommands);
839 AddCommand('rcon_password', GameCommands);
840 AddCommand('rcon', GameCommands);
841 AddCommand('callvote', GameCommands);
842 AddCommand('vote', GameCommands);
843 AddCommand('clientlist', GameCommands);
844 AddCommand('event', GameCommands);
845 AddCommand('screenshot', GameCommands);
846 AddCommand('togglechat', GameCommands);
847 AddCommand('toggleteamchat', GameCommands);
848 AddCommand('weapon', GameCommands);
849 AddCommand('p1_weapon', GameCommands);
850 AddCommand('p2_weapon', GameCommands);
852 AddCommand('god', GameCheats);
853 AddCommand('notarget', GameCheats);
854 AddCommand('give', GameCheats); // "exit" too ;-)
855 AddCommand('open', GameCheats);
856 AddCommand('fly', GameCheats);
857 AddCommand('noclip', GameCheats);
858 AddCommand('speedy', GameCheats);
859 AddCommand('jumpy', GameCheats);
860 AddCommand('noreload', GameCheats);
861 AddCommand('aimline', GameCheats);
862 AddCommand('automap', GameCheats);
864 AddAction('jump', ACTION_JUMP);
865 AddAction('moveleft', ACTION_MOVELEFT);
866 AddAction('moveright', ACTION_MOVERIGHT);
867 AddAction('lookup', ACTION_LOOKUP);
868 AddAction('lookdown', ACTION_LOOKDOWN);
869 AddAction('attack', ACTION_ATTACK);
870 AddAction('scores', ACTION_SCORES);
871 AddAction('activate', ACTION_ACTIVATE);
872 AddAction('strafe', ACTION_STRAFE);
873 AddAction('weapnext', ACTION_WEAPNEXT);
874 AddAction('weapprev', ACTION_WEAPPREV);
876 WhitelistCommand('say');
877 WhitelistCommand('tell');
878 WhitelistCommand('overtime');
879 WhitelistCommand('ready');
880 WhitelistCommand('map');
881 WhitelistCommand('nextmap');
882 WhitelistCommand('endmap');
883 WhitelistCommand('restart');
884 WhitelistCommand('kick');
885 WhitelistCommand('ban');
887 WhitelistCommand('addbot');
888 WhitelistCommand('bot_add');
889 WhitelistCommand('bot_addred');
890 WhitelistCommand('bot_addblue');
891 WhitelistCommand('bot_removeall');
893 WhitelistCommand('g_gamemode');
894 WhitelistCommand('g_friendlyfire');
895 WhitelistCommand('g_weaponstay');
896 WhitelistCommand('g_allow_exit');
897 WhitelistCommand('g_allow_monsters');
898 WhitelistCommand('g_scorelimit');
899 WhitelistCommand('g_timelimit');
901 g_Console_ResetBinds;
902 g_Console_ReadConfig(GameDir + '/dfconfig.cfg');
903 g_Console_ReadConfig(GameDir + '/autoexec.cfg');
905 g_Console_Add(Format(_lc[I_CONSOLE_WELCOME], [GAME_VERSION]));
906 g_Console_Add('');
907 end;
909 procedure g_Console_Update();
910 var
911 a, b: Integer;
912 begin
913 if Cons_Shown then
914 begin
915 // Â ïðîöåññå îòêðûòèÿ:
916 if gConsoleShow and (Cons_Y < 0) then
917 begin
918 Cons_Y := Cons_Y+Step;
919 end;
921 // Â ïðîöåññå çàêðûòèÿ:
922 if (not gConsoleShow) and
923 (Cons_Y > -(gScreenHeight div 2)) then
924 Cons_Y := Cons_Y-Step;
926 // Îêîí÷àòåëüíî îòêðûëàñü:
927 if Cons_Y > 0 then
928 Cons_Y := 0;
930 // Îêîí÷àòåëüíî çàêðûëàñü:
931 if Cons_Y <= (-(gScreenHeight div 2)) then
932 begin
933 Cons_Y := -(gScreenHeight div 2);
934 Cons_Shown := False;
935 end;
936 end;
938 a := 0;
939 while a <= High(MsgArray) do
940 begin
941 if MsgArray[a].Time > 0 then
942 begin
943 if MsgArray[a].Time = 1 then
944 begin
945 if a < High(MsgArray) then
946 begin
947 for b := a to High(MsgArray)-1 do
948 MsgArray[b] := MsgArray[b+1];
950 MsgArray[High(MsgArray)].Time := 0;
952 a := a - 1;
953 end;
954 end
955 else
956 Dec(MsgArray[a].Time);
957 end;
959 a := a + 1;
960 end;
961 end;
964 procedure drawConsoleText ();
965 var
966 CWidth, CHeight: Byte;
967 ty: Integer;
968 sp, ep: LongWord;
969 skip: Integer;
971 procedure putLine (sp, ep: LongWord);
972 var
973 p: LongWord;
974 wdt, cw: Integer;
975 begin
976 p := sp;
977 wdt := 0;
978 while p <> ep do
979 begin
980 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
981 if wdt+cw > gScreenWidth-8 then break;
982 //e_TextureFontPrintChar(X, Y: Integer; Ch: Char; FontID: DWORD; Shadow: Boolean = False);
983 Inc(wdt, cw);
984 cbufNext(p);
985 end;
986 if p <> ep then putLine(p, ep); // do rest of the line first
987 // now print our part
988 if skip = 0 then
989 begin
990 ep := p;
991 p := sp;
992 wdt := 2;
993 while p <> ep do
994 begin
995 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
996 e_TextureFontPrintCharEx(wdt, ty, cbufAt(p), gStdFont);
997 Inc(wdt, cw);
998 cbufNext(p);
999 end;
1000 Dec(ty, CHeight);
1001 end
1002 else
1003 begin
1004 Dec(skip);
1005 end;
1006 end;
1008 begin
1009 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1010 ty := (gScreenHeight div 2)-4-2*CHeight-Abs(Cons_Y);
1011 skip := conSkipLines;
1012 cbufLastLine(sp, ep);
1013 repeat
1014 putLine(sp, ep);
1015 if ty+CHeight <= 0 then break;
1016 until not cbufLineUp(sp, ep);
1017 end;
1019 procedure g_Console_Draw();
1020 var
1021 CWidth, CHeight: Byte;
1022 mfW, mfH: Word;
1023 a, b: Integer;
1024 begin
1025 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1027 for a := 0 to High(MsgArray) do
1028 if MsgArray[a].Time > 0 then
1029 e_TextureFontPrintFmt(0, CHeight*a, MsgArray[a].Msg,
1030 gStdFont, True);
1032 if not Cons_Shown then
1033 begin
1034 if gChatShow then
1035 begin
1036 if gChatTeam then
1037 begin
1038 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say team> ' + Line,
1039 gStdFont, 255, 255, 255, 1, True);
1040 e_TextureFontPrintEx((CPos + 9)*CWidth, gScreenHeight - CHeight - 1, '_',
1041 gStdFont, 255, 255, 255, 1, True);
1042 end
1043 else
1044 begin
1045 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say> ' + Line,
1046 gStdFont, 255, 255, 255, 1, True);
1047 e_TextureFontPrintEx((CPos + 4)*CWidth, gScreenHeight - CHeight - 1, '_',
1048 gStdFont, 255, 255, 255, 1, True);
1049 end;
1050 end;
1051 Exit;
1052 end;
1054 if gDebugMode then
1055 begin
1056 e_CharFont_GetSize(gMenuFont, DEBUG_STRING, mfW, mfH);
1057 a := (gScreenWidth - 2*mfW) div 2;
1058 b := Cons_Y + ((gScreenHeight div 2) - 2*mfH) div 2;
1059 e_CharFont_PrintEx(gMenuFont, a div 2, b div 2, DEBUG_STRING,
1060 _RGB(128, 0, 0), 2.0);
1061 end;
1063 e_DrawSize(ID, 0, Cons_Y, Alpha, False, False, gScreenWidth, gScreenHeight div 2);
1064 e_TextureFontPrint(0, Cons_Y+(gScreenHeight div 2)-CHeight-4, '> '+Line, gStdFont);
1066 drawConsoleText();
1067 (*
1068 if ConsoleHistory <> nil then
1069 begin
1070 b := 0;
1071 if CHeight > 0 then
1072 if Length(ConsoleHistory) > ((gScreenHeight div 2) div CHeight)-1 then
1073 b := Length(ConsoleHistory)-((gScreenHeight div 2) div CHeight)+1;
1075 b := Max(b-Offset, 0);
1076 d := Max(High(ConsoleHistory)-Offset, 0);
1078 c := 2;
1079 for a := d downto b do
1080 begin
1081 e_TextureFontPrintFmt(0, (gScreenHeight div 2)-4-c*CHeight-Abs(Cons_Y), ConsoleHistory[a],
1082 gStdFont, True);
1083 c := c + 1;
1084 end;
1085 end;
1086 *)
1088 e_TextureFontPrint((CPos+1)*CWidth, Cons_Y+(gScreenHeight div 2)-21, '_', gStdFont);
1089 end;
1091 procedure g_Console_Switch();
1092 begin
1093 gChatShow := False;
1094 gConsoleShow := not gConsoleShow;
1095 Cons_Shown := True;
1096 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1097 end;
1099 procedure g_Console_Chat_Switch(Team: Boolean = False);
1100 begin
1101 if not g_Game_IsNet then Exit;
1102 gConsoleShow := False;
1103 gChatShow := not gChatShow;
1104 gChatTeam := Team;
1105 Line := '';
1106 CPos := 1;
1107 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1108 end;
1110 procedure g_Console_Char(C: AnsiChar);
1111 begin
1112 // if gChatShow then
1113 // Exit;
1114 Insert(C, Line, CPos);
1115 CPos := CPos + 1;
1116 end;
1119 var
1120 tcomplist: array of AnsiString = nil;
1121 tcompidx: array of Integer = nil;
1123 procedure Complete ();
1124 var
1125 i, c: Integer;
1126 tused: Integer;
1127 ll, lpfx, cmd: AnsiString;
1128 begin
1129 if (Length(Line) = 0) then
1130 begin
1131 g_Console_Add('');
1132 for i := 0 to High(commands) do
1133 begin
1134 // hidden commands are hidden when cheats aren't enabled
1135 if commands[i].hidden and not conIsCheatsEnabled then continue;
1136 if (Length(commands[i].help) > 0) then
1137 begin
1138 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1139 end
1140 else
1141 begin
1142 g_Console_Add(' '+commands[i].cmd);
1143 end;
1144 end;
1145 exit;
1146 end;
1148 ll := LowerCase(Line);
1149 lpfx := '';
1151 if (Length(ll) > 1) and (ll[Length(ll)] = ' ') then
1152 begin
1153 ll := Copy(ll, 0, Length(ll)-1);
1154 for i := 0 to High(commands) do
1155 begin
1156 // hidden commands are hidden when cheats aren't enabled
1157 if commands[i].hidden and not conIsCheatsEnabled then continue;
1158 if (commands[i].cmd = ll) then
1159 begin
1160 if (Length(commands[i].help) > 0) then
1161 begin
1162 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1163 end;
1164 end;
1165 end;
1166 exit;
1167 end;
1169 // build completion list
1170 tused := 0;
1171 for i := 0 to High(commands) do
1172 begin
1173 // hidden commands are hidden when cheats aren't enabled
1174 if commands[i].hidden and not conIsCheatsEnabled then continue;
1175 cmd := commands[i].cmd;
1176 if (Length(cmd) >= Length(ll)) and (ll = Copy(cmd, 0, Length(ll))) then
1177 begin
1178 if (tused = Length(tcomplist)) then
1179 begin
1180 SetLength(tcomplist, Length(tcomplist)+128);
1181 SetLength(tcompidx, Length(tcompidx)+128);
1182 end;
1183 tcomplist[tused] := cmd;
1184 tcompidx[tused] := i;
1185 Inc(tused);
1186 if (Length(cmd) > Length(lpfx)) then lpfx := cmd;
1187 end;
1188 end;
1190 // get longest prefix
1191 for i := 0 to tused-1 do
1192 begin
1193 cmd := tcomplist[i];
1194 for c := 1 to Length(lpfx) do
1195 begin
1196 if (c > Length(cmd)) then break;
1197 if (cmd[c] <> lpfx[c]) then begin lpfx := Copy(lpfx, 0, c-1); break; end;
1198 end;
1199 end;
1201 if (tused = 0) then exit;
1203 if (tused = 1) then
1204 begin
1205 Line := tcomplist[0]+' ';
1206 CPos := Length(Line)+1;
1207 end
1208 else
1209 begin
1210 // has longest prefix?
1211 if (Length(lpfx) > Length(ll)) then
1212 begin
1213 Line := lpfx;
1214 CPos:= Length(Line)+1;
1215 end
1216 else
1217 begin
1218 g_Console_Add('');
1219 for i := 0 to tused-1 do
1220 begin
1221 if (Length(commands[tcompidx[i]].help) > 0) then
1222 begin
1223 g_Console_Add(' '+tcomplist[i]+' -- '+commands[tcompidx[i]].help);
1224 end
1225 else
1226 begin
1227 g_Console_Add(' '+tcomplist[i]);
1228 end;
1229 end;
1230 end;
1231 end;
1232 end;
1235 procedure g_Console_Control(K: Word);
1236 begin
1237 case K of
1238 IK_BACKSPACE:
1239 if (Length(Line) > 0) and (CPos > 1) then
1240 begin
1241 Delete(Line, CPos-1, 1);
1242 CPos := CPos-1;
1243 end;
1244 IK_DELETE:
1245 if (Length(Line) > 0) and (CPos <= Length(Line)) then
1246 Delete(Line, CPos, 1);
1247 IK_LEFT, IK_KPLEFT, VK_LEFT:
1248 if CPos > 1 then
1249 CPos := CPos - 1;
1250 IK_RIGHT, IK_KPRIGHT, VK_RIGHT:
1251 if CPos <= Length(Line) then
1252 CPos := CPos + 1;
1253 IK_RETURN, IK_KPRETURN, VK_OPEN, VK_FIRE:
1254 begin
1255 if Cons_Shown then
1256 g_Console_Process(Line)
1257 else
1258 if gChatShow then
1259 begin
1260 if (Length(Line) > 0) and g_Game_IsNet then
1261 begin
1262 if gChatTeam then
1263 begin
1264 if g_Game_IsClient then
1265 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_TEAM)
1266 else
1267 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1268 NET_CHAT_TEAM, gPlayer1Settings.Team);
1269 end
1270 else
1271 begin
1272 if g_Game_IsClient then
1273 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_PLAYER)
1274 else
1275 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1276 NET_CHAT_PLAYER);
1277 end;
1278 end;
1280 Line := '';
1281 CPos := 1;
1282 gChatShow := False;
1283 gJustChatted := True;
1284 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1285 end;
1286 end;
1287 IK_TAB:
1288 if not gChatShow then
1289 Complete();
1290 IK_DOWN, IK_KPDOWN, VK_DOWN:
1291 if not gChatShow then
1292 if (CommandHistory <> nil) and
1293 (CmdIndex < Length(CommandHistory)) then
1294 begin
1295 if CmdIndex < Length(CommandHistory)-1 then
1296 CmdIndex := CmdIndex + 1;
1297 Line := CommandHistory[CmdIndex];
1298 CPos := Length(Line) + 1;
1299 end;
1300 IK_UP, IK_KPUP, VK_UP:
1301 if not gChatShow then
1302 if (CommandHistory <> nil) and
1303 (CmdIndex <= Length(CommandHistory)) then
1304 begin
1305 if CmdIndex > 0 then
1306 CmdIndex := CmdIndex - 1;
1307 Line := CommandHistory[CmdIndex];
1308 Cpos := Length(Line) + 1;
1309 end;
1310 IK_PAGEUP, IK_KPPAGEUP, VK_PREV: // PgUp
1311 if not gChatShow then Inc(conSkipLines);
1312 IK_PAGEDN, IK_KPPAGEDN, VK_NEXT: // PgDown
1313 if not gChatShow and (conSkipLines > 0) then Dec(conSkipLines);
1314 IK_HOME, IK_KPHOME:
1315 CPos := 1;
1316 IK_END, IK_KPEND:
1317 CPos := Length(Line) + 1;
1318 end;
1319 end;
1321 function GetStr(var Str: AnsiString): AnsiString;
1322 var
1323 a, b: Integer;
1324 begin
1325 Result := '';
1326 if Str[1] = '"' then
1327 begin
1328 for b := 1 to Length(Str) do
1329 if (b = Length(Str)) or (Str[b+1] = '"') then
1330 begin
1331 Result := Copy(Str, 2, b-1);
1332 Delete(Str, 1, b+1);
1333 Str := Trim(Str);
1334 Exit;
1335 end;
1336 end;
1338 for a := 1 to Length(Str) do
1339 if (a = Length(Str)) or (Str[a+1] = ' ') then
1340 begin
1341 Result := Copy(Str, 1, a);
1342 Delete(Str, 1, a+1);
1343 Str := Trim(Str);
1344 Exit;
1345 end;
1346 end;
1348 function ParseString(Str: AnsiString): SSArray;
1349 begin
1350 Result := nil;
1352 Str := Trim(Str);
1354 if Str = '' then
1355 Exit;
1357 while Str <> '' do
1358 begin
1359 SetLength(Result, Length(Result)+1);
1360 Result[High(Result)] := GetStr(Str);
1361 end;
1362 end;
1364 procedure g_Console_Add (L: AnsiString; show: Boolean=false);
1366 procedure conmsg (s: AnsiString);
1367 var
1368 a: Integer;
1369 begin
1370 if length(s) = 0 then exit;
1371 for a := 0 to High(MsgArray) do
1372 begin
1373 with MsgArray[a] do
1374 begin
1375 if Time = 0 then
1376 begin
1377 Msg := s;
1378 Time := MsgTime;
1379 exit;
1380 end;
1381 end;
1382 end;
1383 for a := 0 to High(MsgArray)-1 do MsgArray[a] := MsgArray[a+1];
1384 with MsgArray[High(MsgArray)] do
1385 begin
1386 Msg := L;
1387 Time := MsgTime;
1388 end;
1389 end;
1391 var
1392 f: Integer;
1393 begin
1394 // put it to console
1395 cbufPut(L);
1396 if (length(L) = 0) or ((L[length(L)] <> #10) and (L[length(L)] <> #13)) then cbufPut(#10);
1398 // now show 'em out of console too
1399 show := show and gAllowConsoleMessages;
1400 if show and gShowMessages then
1401 begin
1402 // Âûâîä ñòðîê ñ ïåðåíîñàìè ïî î÷åðåäè
1403 while length(L) > 0 do
1404 begin
1405 f := Pos(#10, L);
1406 if f <= 0 then f := length(L)+1;
1407 conmsg(Copy(L, 1, f-1));
1408 Delete(L, 1, f);
1409 end;
1410 end;
1412 //SetLength(ConsoleHistory, Length(ConsoleHistory)+1);
1413 //ConsoleHistory[High(ConsoleHistory)] := L;
1415 (*
1416 {$IFDEF HEADLESS}
1417 e_WriteLog('CON: ' + L, MSG_NOTIFY);
1418 {$ENDIF}
1419 *)
1420 end;
1423 var
1424 consolewriterLastWasEOL: Boolean = false;
1426 procedure consolewriter (constref buf; len: SizeUInt);
1427 var
1428 b: PByte;
1429 begin
1430 if (len < 1) then exit;
1431 b := PByte(@buf);
1432 consolewriterLastWasEOL := (b[len-1] = 13) or (b[len-1] = 10);
1433 while (len > 0) do
1434 begin
1435 if (b[0] <> 13) and (b[0] <> 10) then
1436 begin
1437 cbufPut(AnsiChar(b[0]));
1438 end
1439 else
1440 begin
1441 if (len > 1) and (b[0] = 13) then begin len -= 1; b += 1; end;
1442 cbufPut(#10);
1443 end;
1444 len -= 1;
1445 b += 1;
1446 end;
1447 end;
1450 // returns formatted string if `writerCB` is `nil`, empty string otherwise
1451 //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
1452 //TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
1453 procedure conwriteln (const s: AnsiString; show: Boolean=false);
1454 begin
1455 g_Console_Add(s, show);
1456 end;
1459 procedure conwritefln (const s: AnsiString; args: array of const; show: Boolean=false);
1460 begin
1461 if show then
1462 begin
1463 g_Console_Add(formatstrf(s, args), true);
1464 end
1465 else
1466 begin
1467 consolewriterLastWasEOL := false;
1468 formatstrf(s, args, consolewriter);
1469 if not consolewriterLastWasEOL then cbufPut(#10);
1470 end;
1471 end;
1474 procedure g_Console_Clear();
1475 begin
1476 //ConsoleHistory := nil;
1477 cbufClear();
1478 conSkipLines := 0;
1479 end;
1481 procedure AddToHistory(L: AnsiString);
1482 var
1483 len: Integer;
1484 begin
1485 len := Length(CommandHistory);
1487 if (len = 0) or
1488 (LowerCase(CommandHistory[len-1]) <> LowerCase(L)) then
1489 begin
1490 SetLength(CommandHistory, len+1);
1491 CommandHistory[len] := L;
1492 end;
1494 CmdIndex := Length(CommandHistory);
1495 end;
1497 function g_Console_CommandBlacklisted(C: AnsiString): Boolean;
1498 var
1499 Arr: SSArray;
1500 i: Integer;
1501 begin
1502 Result := True;
1504 Arr := nil;
1506 if Trim(C) = '' then
1507 Exit;
1509 Arr := ParseString(C);
1510 if Arr = nil then
1511 Exit;
1513 for i := 0 to High(Whitelist) do
1514 if Whitelist[i] = LowerCase(Arr[0]) then
1515 Result := False;
1516 end;
1518 procedure g_Console_Process(L: AnsiString; quiet: Boolean = False);
1519 var
1520 Arr: SSArray;
1521 i: Integer;
1522 begin
1523 Arr := nil;
1525 if Trim(L) = '' then
1526 Exit;
1528 conSkipLines := 0; // "unscroll"
1530 if L = 'goobers' then
1531 begin
1532 Line := '';
1533 CPos := 1;
1534 gCheats := true;
1535 g_Console_Add('Your memory serves you well.');
1536 exit;
1537 end;
1539 if not quiet then
1540 begin
1541 g_Console_Add('> '+L);
1542 Line := '';
1543 CPos := 1;
1544 end;
1546 Arr := ParseString(L);
1547 if Arr = nil then
1548 Exit;
1550 if commands = nil then
1551 Exit;
1553 if not quiet then
1554 AddToHistory(L);
1556 for i := 0 to High(commands) do
1557 begin
1558 if commands[i].cmd = LowerCase(Arr[0]) then
1559 begin
1560 if commands[i].action >= 0 then
1561 begin
1562 if bindProcess then
1563 begin
1564 if bindDown then
1565 gPlayerAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+'
1566 else
1567 gPlayerAction[commands[i].player, commands[i].action] := gDefaultAction[commands[i].player, commands[i].action]
1568 end
1569 else
1570 begin
1571 gPlayerAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+';
1572 gDefaultAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+'
1573 end;
1574 exit
1575 end
1576 else if bindProcess and not bindDown then
1577 begin
1578 (* command is not action, so do not execute it again after button release *)
1579 exit
1580 end;
1582 if assigned(commands[i].procEx) then
1583 begin
1584 commands[i].procEx(@commands[i], Arr);
1585 exit
1586 end;
1587 if assigned(commands[i].proc) then
1588 begin
1589 commands[i].proc(Arr);
1590 exit
1591 end
1592 end
1593 end;
1595 g_Console_Add(Format(_lc[I_CONSOLE_UNKNOWN], [Arr[0]]));
1596 end;
1599 function g_Console_Interactive: Boolean;
1600 begin
1601 Result := not bindProcess
1602 end;
1604 procedure g_Console_BindKey (key: Integer; cmd: AnsiString);
1605 begin
1606 //e_LogWritefln('bind "%s" "%s" <%s>', [LowerCase(e_KeyNames[key]), cmd, key]);
1607 ASSERT(key >= 0);
1608 ASSERT(key < e_MaxInputKeys);
1609 if key > 0 then
1610 gInputBinds[key].commands := ParseAlias(cmd)
1611 end;
1613 function g_Console_FindBind (n: Integer; cmd: AnsiString): Integer;
1614 var i: Integer;
1615 begin
1616 ASSERT(n >= 1);
1617 result := 0;
1618 if commands = nil then Exit;
1619 i := 0;
1620 cmd := LowerCase(cmd);
1621 while (n >= 1) and (i < e_MaxInputKeys) do
1622 begin
1623 if (Length(gInputBinds[i].commands) = 1) and (gInputBinds[i].commands[0] = cmd) then
1624 begin
1625 result := i;
1626 dec(n)
1627 end;
1628 inc(i)
1629 end;
1630 if n >= 1 then
1631 result := 0
1632 end;
1634 function g_Console_Action (action: Integer): Boolean;
1635 var i, len: Integer;
1636 begin
1637 ASSERT(action >= FIRST_ACTION);
1638 ASSERT(action <= LAST_ACTION);
1639 i := 0;
1640 len := Length(gPlayerAction);
1641 while (i < len) and (not gPlayerAction[i, action]) do inc(i);
1642 Result := i < len
1643 end;
1645 procedure g_Console_ProcessBind (key: Integer; down: Boolean);
1646 var i: Integer;
1647 begin
1648 if (not gChatShow) and (not gConsoleShow) and (key >= 0) and (key < e_MaxInputKeys) and (gInputBinds[key].commands <> nil) then
1649 begin
1650 bindDown := down;
1651 bindProcess := True;
1652 for i := 0 to High(gInputBinds[key].commands) do
1653 g_Console_Process(gInputBinds[key].commands[i], True);
1654 bindProcess := False;
1655 end
1656 end;
1658 procedure g_Console_ResetBinds;
1659 var i: Integer;
1660 begin
1661 for i := 0 to e_MaxInputKeys - 1 do
1662 g_Console_BindKey(i, '');
1664 g_Console_BindKey(IK_A, '+p1_moveleft');
1665 g_Console_BindKey(IK_D, '+p1_moveright');
1666 g_Console_BindKey(IK_W, '+p1_lookup');
1667 g_Console_BindKey(IK_S, '+p1_lookdown');
1668 g_Console_BindKey(IK_SPACE, '+p1_jump');
1669 g_Console_BindKey(IK_H, '+p1_attack');
1670 g_Console_BindKey(IK_J, '+p1_activate');
1671 g_Console_BindKey(IK_E, '+p1_weapnext');
1672 g_Console_BindKey(IK_Q, '+p1_weapprev');
1673 g_Console_BindKey(IK_ALT, '+p1_strafe');
1674 g_Console_BindKey(IK_1, 'p1_weapon 1');
1675 g_Console_BindKey(IK_2, 'p1_weapon 2');
1676 g_Console_BindKey(IK_3, 'p1_weapon 3');
1677 g_Console_BindKey(IK_4, 'p1_weapon 4');
1678 g_Console_BindKey(IK_5, 'p1_weapon 5');
1679 g_Console_BindKey(IK_6, 'p1_weapon 6');
1680 g_Console_BindKey(IK_7, 'p1_weapon 7');
1681 g_Console_BindKey(IK_8, 'p1_weapon 8');
1682 g_Console_BindKey(IK_9, 'p1_weapon 9');
1683 g_Console_BindKey(IK_0, 'p1_weapon 10');
1684 g_Console_BindKey(IK_MINUS, 'p1_weapon 11');
1685 g_Console_BindKey(IK_T, 'togglechat');
1686 g_Console_BindKey(IK_Y, 'toggleteamchat');
1687 g_Console_BindKey(IK_F11, 'screenshot');
1688 g_Console_BindKey(IK_TAB, '+p1_scores');
1689 g_Console_BindKey(IK_PAUSE, 'pause');
1690 g_Console_BindKey(IK_F1, 'vote');
1692 (* for i := 0 to e_MaxJoys - 1 do *)
1693 for i := 0 to 1 do
1694 begin
1695 g_Console_BindKey(e_JoyAxisToKey(i, 0, 0), '+p' + IntToStr(i mod 2 + 1) + '_moveleft');
1696 g_Console_BindKey(e_JoyAxisToKey(i, 0, 1), '+p' + IntToStr(i mod 2 + 1) + '_moveright');
1697 g_Console_BindKey(e_JoyAxisToKey(i, 1, 0), '+p' + IntToStr(i mod 2 + 1) + '_lookup');
1698 g_Console_BindKey(e_JoyAxisToKey(i, 1, 1), '+p' + IntToStr(i mod 2 + 1) + '_lookdown');
1699 g_Console_BindKey(e_JoyButtonToKey(i, 2), '+p' + IntToStr(i mod 2 + 1) + '_jump');
1700 g_Console_BindKey(e_JoyButtonToKey(i, 0), '+p' + IntToStr(i mod 2 + 1) + '_attack');
1701 g_Console_BindKey(e_JoyButtonToKey(i, 3), '+p' + IntToStr(i mod 2 + 1) + '_activate');
1702 g_Console_BindKey(e_JoyButtonToKey(i, 1), '+p' + IntToStr(i mod 2 + 1) + '_weapnext');
1703 g_Console_BindKey(e_JoyButtonToKey(i, 4), '+p' + IntToStr(i mod 2 + 1) + '_weapprev');
1704 g_Console_BindKey(e_JoyButtonToKey(i, 7), '+p' + IntToStr(i mod 2 + 1) + '_strafe');
1705 end;
1707 g_Console_BindKey(VK_LEFT, '+moveleft');
1708 g_Console_BindKey(VK_RIGHT, '+moveright');
1709 g_Console_BindKey(VK_UP, '+lookup');
1710 g_Console_BindKey(VK_DOWN, '+lookdown');
1711 g_Console_BindKey(VK_JUMP, '+jump');
1712 g_Console_BindKey(VK_FIRE, '+attack');
1713 g_Console_BindKey(VK_OPEN, '+activate');
1714 g_Console_BindKey(VK_NEXT, '+weapnext');
1715 g_Console_BindKey(VK_PREV, '+weapprev');
1716 g_Console_BindKey(VK_STRAFE, '+strafe');
1717 g_Console_BindKey(VK_0, 'weapon 1');
1718 g_Console_BindKey(VK_1, 'weapon 2');
1719 g_Console_BindKey(VK_2, 'weapon 3');
1720 g_Console_BindKey(VK_3, 'weapon 4');
1721 g_Console_BindKey(VK_4, 'weapon 5');
1722 g_Console_BindKey(VK_5, 'weapon 6');
1723 g_Console_BindKey(VK_6, 'weapon 7');
1724 g_Console_BindKey(VK_7, 'weapon 8');
1725 g_Console_BindKey(VK_8, 'weapon 9');
1726 g_Console_BindKey(VK_9, 'weapon 10');
1727 g_Console_BindKey(VK_A, 'weapon 11');
1728 g_Console_BindKey(VK_CHAT, 'togglechat');
1729 g_Console_BindKey(VK_TEAM, 'toggleteamchat');
1730 g_Console_BindKey(VK_PRINTSCR, 'screenshot');
1731 g_Console_BindKey(VK_STATUS, '+scores');
1733 // VK_CONSOLE
1734 // VK_ESCAPE
1735 end;
1737 procedure g_Console_ReadConfig (filename: String);
1738 var f: TextFile; s: AnsiString; i, len: Integer;
1739 begin
1740 if FileExists(filename) then
1741 begin
1742 AssignFile(f, filename);
1743 Reset(f);
1744 while not EOF(f) do
1745 begin
1746 ReadLn(f, s);
1747 len := Length(s);
1748 if len > 0 then
1749 begin
1750 i := 1;
1751 (* skip spaces *)
1752 while (i <= len) and (s[i] <= ' ') do inc(i);
1753 (* skip comments *)
1754 if (i <= len) and ((s[i] <> '#') and ((i + 1 > len) or (s[i] <> '/') or (s[i + 1] <> '/'))) then
1755 g_Console_Process(s, True)
1756 end
1757 end;
1758 CloseFile(f)
1759 end
1760 end;
1762 procedure g_Console_WriteConfig (filename: String);
1763 var f: TextFile; i, j: Integer; act: AnsiString;
1764 begin
1765 AssignFile(f, filename);
1766 Rewrite(f);
1767 WriteLn(f, '// generated by doom2d, do not modify');
1768 WriteLn(f, 'unbindall');
1769 for i := 0 to e_MaxInputKeys - 1 do
1770 begin
1771 if Length(gInputBinds[i].commands) > 0 then
1772 begin
1773 act := gInputBinds[i].commands[0];
1774 for j := 1 to High(gInputBinds[i].commands) do
1775 act := act + '; ' + gInputBinds[i].commands[j];
1776 WriteLn(f, 'bind ', e_KeyNames[i], ' "', act, '"')
1777 end
1778 end;
1779 for i := 0 to High(commands) do
1780 begin
1781 if not commands[i].cheat then
1782 begin
1783 if @commands[i].procEx = @boolVarHandler then
1784 begin
1785 if PBoolean(commands[i].ptr)^ then j := 1 else j := 0;
1786 WriteLn(f, commands[i].cmd, ' "', j, '"')
1787 end
1788 else if @commands[i].procEx = @intVarHandler then
1789 begin
1790 WriteLn(f, commands[i].cmd, ' "', PInteger(commands[i].ptr)^, '"')
1791 end
1792 else if @commands[i].procEx = @singleVarHandler then
1793 begin
1794 WriteLn(f, commands[i].cmd, ' "', PVarSingle(commands[i].ptr).val^:0:6, '"')
1795 end
1796 end
1797 end;
1798 CloseFile(f)
1799 end;
1802 end.