DEADSOFTWARE

key binds and vars are stored in 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_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('"' + LowerCase(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 'bindkeys':
662 for i := 0 to e_MaxInputKeys - 1 do
663 if e_KeyNames[i] <> '' then
664 g_Console_Add(LowerCase(e_KeyNames[i]));
665 end
666 end;
668 procedure AddCommand(cmd: AnsiString; proc: TCmdProc; ahelp: AnsiString=''; ahidden: Boolean=false; acheat: Boolean=false);
669 var
670 a: Integer;
671 cp: PCommand;
672 begin
673 SetLength(commands, Length(commands)+1);
674 a := High(commands);
675 cp := @commands[a];
676 cp.cmd := LowerCase(cmd);
677 cp.proc := proc;
678 cp.procEx := nil;
679 cp.help := ahelp;
680 cp.hidden := ahidden;
681 cp.ptr := nil;
682 cp.msg := '';
683 cp.cheat := acheat;
684 cp.action := -1;
685 cp.player := -1;
686 end;
688 procedure AddAction (cmd: AnsiString; action: Integer; help: AnsiString = ''; hidden: Boolean = False; cheat: Boolean = False);
689 const
690 PrefixList: array [0..1] of AnsiString = ('+', '-');
691 PlayerList: array [0..1] of Integer = (1, 2);
692 var
693 s: AnsiString;
694 i: Integer;
696 procedure NewAction (cmd: AnsiString; player: Integer);
697 var cp: PCommand;
698 begin
699 SetLength(commands, Length(commands) + 1);
700 cp := @commands[High(commands)];
701 cp.cmd := LowerCase(cmd);
702 cp.proc := nil;
703 cp.procEx := nil;
704 cp.help := help;
705 cp.hidden := hidden;
706 cp.ptr := nil;
707 cp.msg := '';
708 cp.cheat := cheat;
709 cp.action := action;
710 cp.player := player;
711 end;
713 begin
714 ASSERT(action >= FIRST_ACTION);
715 ASSERT(action <= LAST_ACTION);
716 for s in PrefixList do
717 begin
718 NewAction(s + cmd, 0);
719 for i in PlayerList do
720 NewAction(s + 'p' + IntToStr(i) + '_' + cmd, i - 1)
721 end
722 end;
724 procedure g_Console_Init();
725 var
726 a: Integer;
727 begin
728 g_Texture_CreateWAD(ID, GameWAD+':TEXTURES\CONSOLE');
729 Cons_Y := -(gScreenHeight div 2);
730 gConsoleShow := False;
731 gChatShow := False;
732 Cons_Shown := False;
733 CPos := 1;
735 for a := 0 to High(MsgArray) do
736 with MsgArray[a] do
737 begin
738 Msg := '';
739 Time := 0;
740 end;
742 AddCommand('segfault', segfault, 'make segfault');
744 AddCommand('bind', BindCommands);
745 AddCommand('bindlist', BindCommands);
746 AddCommand('unbind', BindCommands);
747 AddCommand('unbindall', BindCommands);
748 AddCommand('bindkeys', BindCommands);
750 AddCommand('clear', ConsoleCommands, 'clear console');
751 AddCommand('clearhistory', ConsoleCommands);
752 AddCommand('showhistory', ConsoleCommands);
753 AddCommand('commands', ConsoleCommands);
754 AddCommand('time', ConsoleCommands);
755 AddCommand('date', ConsoleCommands);
756 AddCommand('echo', ConsoleCommands);
757 AddCommand('dump', ConsoleCommands);
758 AddCommand('exec', ConsoleCommands);
759 AddCommand('writeconfig', ConsoleCommands);
760 AddCommand('alias', ConsoleCommands);
761 AddCommand('call', ConsoleCommands);
762 AddCommand('ver', ConsoleCommands);
763 AddCommand('version', ConsoleCommands);
765 AddCommand('d_window', DebugCommands);
766 AddCommand('d_sounds', DebugCommands);
767 AddCommand('d_frames', DebugCommands);
768 AddCommand('d_winmsg', DebugCommands);
769 AddCommand('d_monoff', DebugCommands);
770 AddCommand('d_botoff', DebugCommands);
771 AddCommand('d_monster', DebugCommands);
772 AddCommand('d_health', DebugCommands);
773 AddCommand('d_player', DebugCommands);
774 AddCommand('d_joy', DebugCommands);
775 AddCommand('d_mem', DebugCommands);
777 AddCommand('p1_name', GameCVars);
778 AddCommand('p2_name', GameCVars);
779 AddCommand('p1_color', GameCVars);
780 AddCommand('p2_color', GameCVars);
781 AddCommand('r_showtime', GameCVars);
782 AddCommand('r_showscore', GameCVars);
783 AddCommand('r_showlives', GameCVars);
784 AddCommand('r_showstat', GameCVars);
785 AddCommand('r_showkillmsg', GameCVars);
786 AddCommand('r_showspect', GameCVars);
787 AddCommand('r_showping', GameCVars);
788 AddCommand('g_gamemode', GameCVars);
789 AddCommand('g_friendlyfire', GameCVars);
790 AddCommand('g_weaponstay', GameCVars);
791 AddCommand('g_allow_exit', GameCVars);
792 AddCommand('g_allow_monsters', GameCVars);
793 AddCommand('g_bot_vsmonsters', GameCVars);
794 AddCommand('g_bot_vsplayers', GameCVars);
795 AddCommand('g_scorelimit', GameCVars);
796 AddCommand('g_timelimit', GameCVars);
797 AddCommand('g_maxlives', GameCVars);
798 AddCommand('g_warmuptime', GameCVars);
799 AddCommand('net_interp', GameCVars);
800 AddCommand('net_forceplayerupdate', GameCVars);
801 AddCommand('net_predictself', GameCVars);
802 AddCommand('sv_name', GameCVars);
803 AddCommand('sv_passwd', GameCVars);
804 AddCommand('sv_maxplrs', GameCVars);
805 AddCommand('sv_public', GameCVars);
806 AddCommand('sv_intertime', GameCVars);
808 AddCommand('quit', GameCommands);
809 AddCommand('exit', GameCommands);
810 AddCommand('pause', GameCommands);
811 AddCommand('endgame', GameCommands);
812 AddCommand('restart', GameCommands);
813 AddCommand('addbot', GameCommands);
814 AddCommand('bot_add', GameCommands);
815 AddCommand('bot_addlist', GameCommands);
816 AddCommand('bot_addred', GameCommands);
817 AddCommand('bot_addblue', GameCommands);
818 AddCommand('bot_removeall', GameCommands);
819 AddCommand('chat', GameCommands);
820 AddCommand('teamchat', GameCommands);
821 AddCommand('game', GameCommands);
822 AddCommand('host', GameCommands);
823 AddCommand('map', GameCommands);
824 AddCommand('nextmap', GameCommands);
825 AddCommand('endmap', GameCommands);
826 AddCommand('goodbye', GameCommands);
827 AddCommand('suicide', GameCommands);
828 AddCommand('spectate', GameCommands);
829 AddCommand('ready', GameCommands);
830 AddCommand('kick', GameCommands);
831 AddCommand('kick_id', GameCommands);
832 AddCommand('ban', GameCommands);
833 AddCommand('permban', GameCommands);
834 AddCommand('ban_id', GameCommands);
835 AddCommand('permban_id', GameCommands);
836 AddCommand('unban', GameCommands);
837 AddCommand('connect', GameCommands);
838 AddCommand('disconnect', GameCommands);
839 AddCommand('reconnect', GameCommands);
840 AddCommand('say', GameCommands);
841 AddCommand('tell', GameCommands);
842 AddCommand('overtime', GameCommands);
843 AddCommand('rcon_password', GameCommands);
844 AddCommand('rcon', GameCommands);
845 AddCommand('callvote', GameCommands);
846 AddCommand('vote', GameCommands);
847 AddCommand('clientlist', GameCommands);
848 AddCommand('event', GameCommands);
849 AddCommand('screenshot', GameCommands);
850 AddCommand('togglechat', GameCommands);
851 AddCommand('toggleteamchat', GameCommands);
852 AddCommand('weapon', GameCommands);
853 AddCommand('p1_weapon', GameCommands);
854 AddCommand('p2_weapon', GameCommands);
856 AddCommand('god', GameCheats);
857 AddCommand('notarget', GameCheats);
858 AddCommand('give', GameCheats); // "exit" too ;-)
859 AddCommand('open', GameCheats);
860 AddCommand('fly', GameCheats);
861 AddCommand('noclip', GameCheats);
862 AddCommand('speedy', GameCheats);
863 AddCommand('jumpy', GameCheats);
864 AddCommand('noreload', GameCheats);
865 AddCommand('aimline', GameCheats);
866 AddCommand('automap', GameCheats);
868 AddAction('jump', ACTION_JUMP);
869 AddAction('moveleft', ACTION_MOVELEFT);
870 AddAction('moveright', ACTION_MOVERIGHT);
871 AddAction('lookup', ACTION_LOOKUP);
872 AddAction('lookdown', ACTION_LOOKDOWN);
873 AddAction('attack', ACTION_ATTACK);
874 AddAction('scores', ACTION_SCORES);
875 AddAction('activate', ACTION_ACTIVATE);
876 AddAction('strafe', ACTION_STRAFE);
877 AddAction('weapnext', ACTION_WEAPNEXT);
878 AddAction('weapprev', ACTION_WEAPPREV);
880 WhitelistCommand('say');
881 WhitelistCommand('tell');
882 WhitelistCommand('overtime');
883 WhitelistCommand('ready');
884 WhitelistCommand('map');
885 WhitelistCommand('nextmap');
886 WhitelistCommand('endmap');
887 WhitelistCommand('restart');
888 WhitelistCommand('kick');
889 WhitelistCommand('ban');
891 WhitelistCommand('addbot');
892 WhitelistCommand('bot_add');
893 WhitelistCommand('bot_addred');
894 WhitelistCommand('bot_addblue');
895 WhitelistCommand('bot_removeall');
897 WhitelistCommand('g_gamemode');
898 WhitelistCommand('g_friendlyfire');
899 WhitelistCommand('g_weaponstay');
900 WhitelistCommand('g_allow_exit');
901 WhitelistCommand('g_allow_monsters');
902 WhitelistCommand('g_scorelimit');
903 WhitelistCommand('g_timelimit');
905 g_Console_ResetBinds;
906 g_Console_ReadConfig(GameDir + '/dfconfig.cfg');
907 g_Console_ReadConfig(GameDir + '/autoexec.cfg');
909 g_Console_Add(Format(_lc[I_CONSOLE_WELCOME], [GAME_VERSION]));
910 g_Console_Add('');
911 end;
913 procedure g_Console_Update();
914 var
915 a, b: Integer;
916 begin
917 if Cons_Shown then
918 begin
919 // Â ïðîöåññå îòêðûòèÿ:
920 if gConsoleShow and (Cons_Y < 0) then
921 begin
922 Cons_Y := Cons_Y+Step;
923 end;
925 // Â ïðîöåññå çàêðûòèÿ:
926 if (not gConsoleShow) and
927 (Cons_Y > -(gScreenHeight div 2)) then
928 Cons_Y := Cons_Y-Step;
930 // Îêîí÷àòåëüíî îòêðûëàñü:
931 if Cons_Y > 0 then
932 Cons_Y := 0;
934 // Îêîí÷àòåëüíî çàêðûëàñü:
935 if Cons_Y <= (-(gScreenHeight div 2)) then
936 begin
937 Cons_Y := -(gScreenHeight div 2);
938 Cons_Shown := False;
939 end;
940 end;
942 a := 0;
943 while a <= High(MsgArray) do
944 begin
945 if MsgArray[a].Time > 0 then
946 begin
947 if MsgArray[a].Time = 1 then
948 begin
949 if a < High(MsgArray) then
950 begin
951 for b := a to High(MsgArray)-1 do
952 MsgArray[b] := MsgArray[b+1];
954 MsgArray[High(MsgArray)].Time := 0;
956 a := a - 1;
957 end;
958 end
959 else
960 Dec(MsgArray[a].Time);
961 end;
963 a := a + 1;
964 end;
965 end;
968 procedure drawConsoleText ();
969 var
970 CWidth, CHeight: Byte;
971 ty: Integer;
972 sp, ep: LongWord;
973 skip: Integer;
975 procedure putLine (sp, ep: LongWord);
976 var
977 p: LongWord;
978 wdt, cw: Integer;
979 begin
980 p := sp;
981 wdt := 0;
982 while p <> ep do
983 begin
984 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
985 if wdt+cw > gScreenWidth-8 then break;
986 //e_TextureFontPrintChar(X, Y: Integer; Ch: Char; FontID: DWORD; Shadow: Boolean = False);
987 Inc(wdt, cw);
988 cbufNext(p);
989 end;
990 if p <> ep then putLine(p, ep); // do rest of the line first
991 // now print our part
992 if skip = 0 then
993 begin
994 ep := p;
995 p := sp;
996 wdt := 2;
997 while p <> ep do
998 begin
999 cw := e_TextureFontCharWidth(cbufAt(p), gStdFont);
1000 e_TextureFontPrintCharEx(wdt, ty, cbufAt(p), gStdFont);
1001 Inc(wdt, cw);
1002 cbufNext(p);
1003 end;
1004 Dec(ty, CHeight);
1005 end
1006 else
1007 begin
1008 Dec(skip);
1009 end;
1010 end;
1012 begin
1013 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1014 ty := (gScreenHeight div 2)-4-2*CHeight-Abs(Cons_Y);
1015 skip := conSkipLines;
1016 cbufLastLine(sp, ep);
1017 repeat
1018 putLine(sp, ep);
1019 if ty+CHeight <= 0 then break;
1020 until not cbufLineUp(sp, ep);
1021 end;
1023 procedure g_Console_Draw();
1024 var
1025 CWidth, CHeight: Byte;
1026 mfW, mfH: Word;
1027 a, b: Integer;
1028 begin
1029 e_TextureFontGetSize(gStdFont, CWidth, CHeight);
1031 for a := 0 to High(MsgArray) do
1032 if MsgArray[a].Time > 0 then
1033 e_TextureFontPrintFmt(0, CHeight*a, MsgArray[a].Msg,
1034 gStdFont, True);
1036 if not Cons_Shown then
1037 begin
1038 if gChatShow then
1039 begin
1040 if gChatTeam then
1041 begin
1042 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say team> ' + Line,
1043 gStdFont, 255, 255, 255, 1, True);
1044 e_TextureFontPrintEx((CPos + 9)*CWidth, gScreenHeight - CHeight - 1, '_',
1045 gStdFont, 255, 255, 255, 1, True);
1046 end
1047 else
1048 begin
1049 e_TextureFontPrintEx(0, gScreenHeight - CHeight - 1, 'say> ' + Line,
1050 gStdFont, 255, 255, 255, 1, True);
1051 e_TextureFontPrintEx((CPos + 4)*CWidth, gScreenHeight - CHeight - 1, '_',
1052 gStdFont, 255, 255, 255, 1, True);
1053 end;
1054 end;
1055 Exit;
1056 end;
1058 if gDebugMode then
1059 begin
1060 e_CharFont_GetSize(gMenuFont, DEBUG_STRING, mfW, mfH);
1061 a := (gScreenWidth - 2*mfW) div 2;
1062 b := Cons_Y + ((gScreenHeight div 2) - 2*mfH) div 2;
1063 e_CharFont_PrintEx(gMenuFont, a div 2, b div 2, DEBUG_STRING,
1064 _RGB(128, 0, 0), 2.0);
1065 end;
1067 e_DrawSize(ID, 0, Cons_Y, Alpha, False, False, gScreenWidth, gScreenHeight div 2);
1068 e_TextureFontPrint(0, Cons_Y+(gScreenHeight div 2)-CHeight-4, '> '+Line, gStdFont);
1070 drawConsoleText();
1071 (*
1072 if ConsoleHistory <> nil then
1073 begin
1074 b := 0;
1075 if CHeight > 0 then
1076 if Length(ConsoleHistory) > ((gScreenHeight div 2) div CHeight)-1 then
1077 b := Length(ConsoleHistory)-((gScreenHeight div 2) div CHeight)+1;
1079 b := Max(b-Offset, 0);
1080 d := Max(High(ConsoleHistory)-Offset, 0);
1082 c := 2;
1083 for a := d downto b do
1084 begin
1085 e_TextureFontPrintFmt(0, (gScreenHeight div 2)-4-c*CHeight-Abs(Cons_Y), ConsoleHistory[a],
1086 gStdFont, True);
1087 c := c + 1;
1088 end;
1089 end;
1090 *)
1092 e_TextureFontPrint((CPos+1)*CWidth, Cons_Y+(gScreenHeight div 2)-21, '_', gStdFont);
1093 end;
1095 procedure g_Console_Switch();
1096 begin
1097 gChatShow := False;
1098 gConsoleShow := not gConsoleShow;
1099 Cons_Shown := True;
1100 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1101 end;
1103 procedure g_Console_Chat_Switch(Team: Boolean = False);
1104 begin
1105 if not g_Game_IsNet then Exit;
1106 gConsoleShow := False;
1107 gChatShow := not gChatShow;
1108 gChatTeam := Team;
1109 Line := '';
1110 CPos := 1;
1111 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1112 end;
1114 procedure g_Console_Char(C: AnsiChar);
1115 begin
1116 // if gChatShow then
1117 // Exit;
1118 Insert(C, Line, CPos);
1119 CPos := CPos + 1;
1120 end;
1123 var
1124 tcomplist: array of AnsiString = nil;
1125 tcompidx: array of Integer = nil;
1127 procedure Complete ();
1128 var
1129 i, c: Integer;
1130 tused: Integer;
1131 ll, lpfx, cmd: AnsiString;
1132 begin
1133 if (Length(Line) = 0) then
1134 begin
1135 g_Console_Add('');
1136 for i := 0 to High(commands) do
1137 begin
1138 // hidden commands are hidden when cheats aren't enabled
1139 if commands[i].hidden and not conIsCheatsEnabled then continue;
1140 if (Length(commands[i].help) > 0) then
1141 begin
1142 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1143 end
1144 else
1145 begin
1146 g_Console_Add(' '+commands[i].cmd);
1147 end;
1148 end;
1149 exit;
1150 end;
1152 ll := LowerCase(Line);
1153 lpfx := '';
1155 if (Length(ll) > 1) and (ll[Length(ll)] = ' ') then
1156 begin
1157 ll := Copy(ll, 0, Length(ll)-1);
1158 for i := 0 to High(commands) do
1159 begin
1160 // hidden commands are hidden when cheats aren't enabled
1161 if commands[i].hidden and not conIsCheatsEnabled then continue;
1162 if (commands[i].cmd = ll) then
1163 begin
1164 if (Length(commands[i].help) > 0) then
1165 begin
1166 g_Console_Add(' '+commands[i].cmd+' -- '+commands[i].help);
1167 end;
1168 end;
1169 end;
1170 exit;
1171 end;
1173 // build completion list
1174 tused := 0;
1175 for i := 0 to High(commands) do
1176 begin
1177 // hidden commands are hidden when cheats aren't enabled
1178 if commands[i].hidden and not conIsCheatsEnabled then continue;
1179 cmd := commands[i].cmd;
1180 if (Length(cmd) >= Length(ll)) and (ll = Copy(cmd, 0, Length(ll))) then
1181 begin
1182 if (tused = Length(tcomplist)) then
1183 begin
1184 SetLength(tcomplist, Length(tcomplist)+128);
1185 SetLength(tcompidx, Length(tcompidx)+128);
1186 end;
1187 tcomplist[tused] := cmd;
1188 tcompidx[tused] := i;
1189 Inc(tused);
1190 if (Length(cmd) > Length(lpfx)) then lpfx := cmd;
1191 end;
1192 end;
1194 // get longest prefix
1195 for i := 0 to tused-1 do
1196 begin
1197 cmd := tcomplist[i];
1198 for c := 1 to Length(lpfx) do
1199 begin
1200 if (c > Length(cmd)) then break;
1201 if (cmd[c] <> lpfx[c]) then begin lpfx := Copy(lpfx, 0, c-1); break; end;
1202 end;
1203 end;
1205 if (tused = 0) then exit;
1207 if (tused = 1) then
1208 begin
1209 Line := tcomplist[0]+' ';
1210 CPos := Length(Line)+1;
1211 end
1212 else
1213 begin
1214 // has longest prefix?
1215 if (Length(lpfx) > Length(ll)) then
1216 begin
1217 Line := lpfx;
1218 CPos:= Length(Line)+1;
1219 end
1220 else
1221 begin
1222 g_Console_Add('');
1223 for i := 0 to tused-1 do
1224 begin
1225 if (Length(commands[tcompidx[i]].help) > 0) then
1226 begin
1227 g_Console_Add(' '+tcomplist[i]+' -- '+commands[tcompidx[i]].help);
1228 end
1229 else
1230 begin
1231 g_Console_Add(' '+tcomplist[i]);
1232 end;
1233 end;
1234 end;
1235 end;
1236 end;
1239 procedure g_Console_Control(K: Word);
1240 begin
1241 case K of
1242 IK_BACKSPACE:
1243 if (Length(Line) > 0) and (CPos > 1) then
1244 begin
1245 Delete(Line, CPos-1, 1);
1246 CPos := CPos-1;
1247 end;
1248 IK_DELETE:
1249 if (Length(Line) > 0) and (CPos <= Length(Line)) then
1250 Delete(Line, CPos, 1);
1251 IK_LEFT, IK_KPLEFT, VK_LEFT:
1252 if CPos > 1 then
1253 CPos := CPos - 1;
1254 IK_RIGHT, IK_KPRIGHT, VK_RIGHT:
1255 if CPos <= Length(Line) then
1256 CPos := CPos + 1;
1257 IK_RETURN, IK_KPRETURN, VK_OPEN, VK_FIRE:
1258 begin
1259 if Cons_Shown then
1260 g_Console_Process(Line)
1261 else
1262 if gChatShow then
1263 begin
1264 if (Length(Line) > 0) and g_Game_IsNet then
1265 begin
1266 if gChatTeam then
1267 begin
1268 if g_Game_IsClient then
1269 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_TEAM)
1270 else
1271 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1272 NET_CHAT_TEAM, gPlayer1Settings.Team);
1273 end
1274 else
1275 begin
1276 if g_Game_IsClient then
1277 MC_SEND_Chat(b_Text_Format(Line), NET_CHAT_PLAYER)
1278 else
1279 MH_SEND_Chat('[' + gPlayer1Settings.name + ']: ' + b_Text_Format(Line),
1280 NET_CHAT_PLAYER);
1281 end;
1282 end;
1284 Line := '';
1285 CPos := 1;
1286 gChatShow := False;
1287 gJustChatted := True;
1288 g_Touch_ShowKeyboard(gConsoleShow or gChatShow);
1289 end;
1290 end;
1291 IK_TAB:
1292 if not gChatShow then
1293 Complete();
1294 IK_DOWN, IK_KPDOWN, VK_DOWN:
1295 if not gChatShow then
1296 if (CommandHistory <> nil) and
1297 (CmdIndex < Length(CommandHistory)) then
1298 begin
1299 if CmdIndex < Length(CommandHistory)-1 then
1300 CmdIndex := CmdIndex + 1;
1301 Line := CommandHistory[CmdIndex];
1302 CPos := Length(Line) + 1;
1303 end;
1304 IK_UP, IK_KPUP, VK_UP:
1305 if not gChatShow then
1306 if (CommandHistory <> nil) and
1307 (CmdIndex <= Length(CommandHistory)) then
1308 begin
1309 if CmdIndex > 0 then
1310 CmdIndex := CmdIndex - 1;
1311 Line := CommandHistory[CmdIndex];
1312 Cpos := Length(Line) + 1;
1313 end;
1314 IK_PAGEUP, IK_KPPAGEUP, VK_PREV: // PgUp
1315 if not gChatShow then Inc(conSkipLines);
1316 IK_PAGEDN, IK_KPPAGEDN, VK_NEXT: // PgDown
1317 if not gChatShow and (conSkipLines > 0) then Dec(conSkipLines);
1318 IK_HOME, IK_KPHOME:
1319 CPos := 1;
1320 IK_END, IK_KPEND:
1321 CPos := Length(Line) + 1;
1322 end;
1323 end;
1325 function GetStr(var Str: AnsiString): AnsiString;
1326 var
1327 a, b: Integer;
1328 begin
1329 Result := '';
1330 if Str[1] = '"' then
1331 begin
1332 for b := 1 to Length(Str) do
1333 if (b = Length(Str)) or (Str[b+1] = '"') then
1334 begin
1335 Result := Copy(Str, 2, b-1);
1336 Delete(Str, 1, b+1);
1337 Str := Trim(Str);
1338 Exit;
1339 end;
1340 end;
1342 for a := 1 to Length(Str) do
1343 if (a = Length(Str)) or (Str[a+1] = ' ') then
1344 begin
1345 Result := Copy(Str, 1, a);
1346 Delete(Str, 1, a+1);
1347 Str := Trim(Str);
1348 Exit;
1349 end;
1350 end;
1352 function ParseString(Str: AnsiString): SSArray;
1353 begin
1354 Result := nil;
1356 Str := Trim(Str);
1358 if Str = '' then
1359 Exit;
1361 while Str <> '' do
1362 begin
1363 SetLength(Result, Length(Result)+1);
1364 Result[High(Result)] := GetStr(Str);
1365 end;
1366 end;
1368 procedure g_Console_Add (L: AnsiString; show: Boolean=false);
1370 procedure conmsg (s: AnsiString);
1371 var
1372 a: Integer;
1373 begin
1374 if length(s) = 0 then exit;
1375 for a := 0 to High(MsgArray) do
1376 begin
1377 with MsgArray[a] do
1378 begin
1379 if Time = 0 then
1380 begin
1381 Msg := s;
1382 Time := MsgTime;
1383 exit;
1384 end;
1385 end;
1386 end;
1387 for a := 0 to High(MsgArray)-1 do MsgArray[a] := MsgArray[a+1];
1388 with MsgArray[High(MsgArray)] do
1389 begin
1390 Msg := L;
1391 Time := MsgTime;
1392 end;
1393 end;
1395 var
1396 f: Integer;
1397 begin
1398 // put it to console
1399 cbufPut(L);
1400 if (length(L) = 0) or ((L[length(L)] <> #10) and (L[length(L)] <> #13)) then cbufPut(#10);
1402 // now show 'em out of console too
1403 show := show and gAllowConsoleMessages;
1404 if show and gShowMessages then
1405 begin
1406 // Âûâîä ñòðîê ñ ïåðåíîñàìè ïî î÷åðåäè
1407 while length(L) > 0 do
1408 begin
1409 f := Pos(#10, L);
1410 if f <= 0 then f := length(L)+1;
1411 conmsg(Copy(L, 1, f-1));
1412 Delete(L, 1, f);
1413 end;
1414 end;
1416 //SetLength(ConsoleHistory, Length(ConsoleHistory)+1);
1417 //ConsoleHistory[High(ConsoleHistory)] := L;
1419 (*
1420 {$IFDEF HEADLESS}
1421 e_WriteLog('CON: ' + L, MSG_NOTIFY);
1422 {$ENDIF}
1423 *)
1424 end;
1427 var
1428 consolewriterLastWasEOL: Boolean = false;
1430 procedure consolewriter (constref buf; len: SizeUInt);
1431 var
1432 b: PByte;
1433 begin
1434 if (len < 1) then exit;
1435 b := PByte(@buf);
1436 consolewriterLastWasEOL := (b[len-1] = 13) or (b[len-1] = 10);
1437 while (len > 0) do
1438 begin
1439 if (b[0] <> 13) and (b[0] <> 10) then
1440 begin
1441 cbufPut(AnsiChar(b[0]));
1442 end
1443 else
1444 begin
1445 if (len > 1) and (b[0] = 13) then begin len -= 1; b += 1; end;
1446 cbufPut(#10);
1447 end;
1448 len -= 1;
1449 b += 1;
1450 end;
1451 end;
1454 // returns formatted string if `writerCB` is `nil`, empty string otherwise
1455 //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
1456 //TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
1457 procedure conwriteln (const s: AnsiString; show: Boolean=false);
1458 begin
1459 g_Console_Add(s, show);
1460 end;
1463 procedure conwritefln (const s: AnsiString; args: array of const; show: Boolean=false);
1464 begin
1465 if show then
1466 begin
1467 g_Console_Add(formatstrf(s, args), true);
1468 end
1469 else
1470 begin
1471 consolewriterLastWasEOL := false;
1472 formatstrf(s, args, consolewriter);
1473 if not consolewriterLastWasEOL then cbufPut(#10);
1474 end;
1475 end;
1478 procedure g_Console_Clear();
1479 begin
1480 //ConsoleHistory := nil;
1481 cbufClear();
1482 conSkipLines := 0;
1483 end;
1485 procedure AddToHistory(L: AnsiString);
1486 var
1487 len: Integer;
1488 begin
1489 len := Length(CommandHistory);
1491 if (len = 0) or
1492 (LowerCase(CommandHistory[len-1]) <> LowerCase(L)) then
1493 begin
1494 SetLength(CommandHistory, len+1);
1495 CommandHistory[len] := L;
1496 end;
1498 CmdIndex := Length(CommandHistory);
1499 end;
1501 function g_Console_CommandBlacklisted(C: AnsiString): Boolean;
1502 var
1503 Arr: SSArray;
1504 i: Integer;
1505 begin
1506 Result := True;
1508 Arr := nil;
1510 if Trim(C) = '' then
1511 Exit;
1513 Arr := ParseString(C);
1514 if Arr = nil then
1515 Exit;
1517 for i := 0 to High(Whitelist) do
1518 if Whitelist[i] = LowerCase(Arr[0]) then
1519 Result := False;
1520 end;
1522 procedure g_Console_Process(L: AnsiString; quiet: Boolean = False);
1523 var
1524 Arr: SSArray;
1525 i: Integer;
1526 begin
1527 Arr := nil;
1529 if Trim(L) = '' then
1530 Exit;
1532 conSkipLines := 0; // "unscroll"
1534 if L = 'goobers' then
1535 begin
1536 Line := '';
1537 CPos := 1;
1538 gCheats := true;
1539 g_Console_Add('Your memory serves you well.');
1540 exit;
1541 end;
1543 if not quiet then
1544 begin
1545 g_Console_Add('> '+L);
1546 Line := '';
1547 CPos := 1;
1548 end;
1550 Arr := ParseString(L);
1551 if Arr = nil then
1552 Exit;
1554 if commands = nil then
1555 Exit;
1557 if not quiet then
1558 AddToHistory(L);
1560 for i := 0 to High(commands) do
1561 begin
1562 if commands[i].cmd = LowerCase(Arr[0]) then
1563 begin
1564 if commands[i].action >= 0 then
1565 begin
1566 if bindProcess then
1567 begin
1568 if bindDown then
1569 gPlayerAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+'
1570 else
1571 gPlayerAction[commands[i].player, commands[i].action] := gDefaultAction[commands[i].player, commands[i].action]
1572 end
1573 else
1574 begin
1575 gPlayerAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+';
1576 gDefaultAction[commands[i].player, commands[i].action] := commands[i].cmd[1] = '+'
1577 end;
1578 exit
1579 end
1580 else if bindProcess and not bindDown then
1581 begin
1582 (* command is not action, so do not execute it again after button release *)
1583 exit
1584 end;
1586 if assigned(commands[i].procEx) then
1587 begin
1588 commands[i].procEx(@commands[i], Arr);
1589 exit
1590 end;
1591 if assigned(commands[i].proc) then
1592 begin
1593 commands[i].proc(Arr);
1594 exit
1595 end
1596 end
1597 end;
1599 g_Console_Add(Format(_lc[I_CONSOLE_UNKNOWN], [Arr[0]]));
1600 end;
1603 function g_Console_Interactive: Boolean;
1604 begin
1605 Result := not bindProcess
1606 end;
1608 procedure g_Console_BindKey (key: Integer; cmd: AnsiString);
1609 begin
1610 //e_LogWritefln('bind "%s" "%s" <%s>', [LowerCase(e_KeyNames[key]), cmd, key]);
1611 ASSERT(key >= 0);
1612 ASSERT(key < e_MaxInputKeys);
1613 if key > 0 then
1614 gInputBinds[key].commands := ParseAlias(cmd)
1615 end;
1617 function g_Console_FindBind (n: Integer; cmd: AnsiString): Integer;
1618 var i: Integer;
1619 begin
1620 ASSERT(n >= 1);
1621 result := 0;
1622 if commands = nil then Exit;
1623 i := 0;
1624 cmd := LowerCase(cmd);
1625 while (n >= 1) and (i < e_MaxInputKeys) do
1626 begin
1627 if (Length(gInputBinds[i].commands) = 1) and (gInputBinds[i].commands[0] = cmd) then
1628 begin
1629 result := i;
1630 dec(n)
1631 end;
1632 inc(i)
1633 end;
1634 if n >= 1 then
1635 result := 0
1636 end;
1638 function g_Console_Action (action: Integer): Boolean;
1639 var i, len: Integer;
1640 begin
1641 ASSERT(action >= FIRST_ACTION);
1642 ASSERT(action <= LAST_ACTION);
1643 i := 0;
1644 len := Length(gPlayerAction);
1645 while (i < len) and (not gPlayerAction[i, action]) do inc(i);
1646 Result := i < len
1647 end;
1649 procedure g_Console_ProcessBind (key: Integer; down: Boolean);
1650 var i: Integer;
1651 begin
1652 if (not gChatShow) and (not gConsoleShow) and (key >= 0) and (key < e_MaxInputKeys) and (gInputBinds[key].commands <> nil) then
1653 begin
1654 bindDown := down;
1655 bindProcess := True;
1656 for i := 0 to High(gInputBinds[key].commands) do
1657 g_Console_Process(gInputBinds[key].commands[i], True);
1658 bindProcess := False;
1659 end
1660 end;
1662 procedure g_Console_ResetBinds;
1663 var i: Integer;
1664 begin
1665 for i := 0 to e_MaxInputKeys - 1 do
1666 g_Console_BindKey(i, '');
1668 g_Console_BindKey(IK_A, '+p1_moveleft');
1669 g_Console_BindKey(IK_D, '+p1_moveright');
1670 g_Console_BindKey(IK_W, '+p1_lookup');
1671 g_Console_BindKey(IK_S, '+p1_lookdown');
1672 g_Console_BindKey(IK_SPACE, '+p1_jump');
1673 g_Console_BindKey(IK_H, '+p1_attack');
1674 g_Console_BindKey(IK_J, '+p1_activate');
1675 g_Console_BindKey(IK_E, '+p1_weapnext');
1676 g_Console_BindKey(IK_Q, '+p1_weapprev');
1677 g_Console_BindKey(IK_ALT, '+p1_strafe');
1678 g_Console_BindKey(IK_1, 'p1_weapon 1');
1679 g_Console_BindKey(IK_2, 'p1_weapon 2');
1680 g_Console_BindKey(IK_3, 'p1_weapon 3');
1681 g_Console_BindKey(IK_4, 'p1_weapon 4');
1682 g_Console_BindKey(IK_5, 'p1_weapon 5');
1683 g_Console_BindKey(IK_6, 'p1_weapon 6');
1684 g_Console_BindKey(IK_7, 'p1_weapon 7');
1685 g_Console_BindKey(IK_8, 'p1_weapon 8');
1686 g_Console_BindKey(IK_9, 'p1_weapon 9');
1687 g_Console_BindKey(IK_0, 'p1_weapon 10');
1688 g_Console_BindKey(IK_MINUS, 'p1_weapon 11');
1689 g_Console_BindKey(IK_T, 'togglechat');
1690 g_Console_BindKey(IK_Y, 'toggleteamchat');
1691 g_Console_BindKey(IK_F11, 'screenshot');
1692 g_Console_BindKey(IK_TAB, '+p1_scores');
1693 g_Console_BindKey(IK_PAUSE, 'pause');
1694 g_Console_BindKey(IK_F1, 'vote');
1696 (* for i := 0 to e_MaxJoys - 1 do *)
1697 for i := 0 to 1 do
1698 begin
1699 g_Console_BindKey(e_JoyAxisToKey(i, 0, 0), '+p' + IntToStr(i mod 2 + 1) + '_moveleft');
1700 g_Console_BindKey(e_JoyAxisToKey(i, 0, 1), '+p' + IntToStr(i mod 2 + 1) + '_moveright');
1701 g_Console_BindKey(e_JoyAxisToKey(i, 1, 0), '+p' + IntToStr(i mod 2 + 1) + '_lookup');
1702 g_Console_BindKey(e_JoyAxisToKey(i, 1, 1), '+p' + IntToStr(i mod 2 + 1) + '_lookdown');
1703 g_Console_BindKey(e_JoyButtonToKey(i, 2), '+p' + IntToStr(i mod 2 + 1) + '_jump');
1704 g_Console_BindKey(e_JoyButtonToKey(i, 0), '+p' + IntToStr(i mod 2 + 1) + '_attack');
1705 g_Console_BindKey(e_JoyButtonToKey(i, 3), '+p' + IntToStr(i mod 2 + 1) + '_activate');
1706 g_Console_BindKey(e_JoyButtonToKey(i, 1), '+p' + IntToStr(i mod 2 + 1) + '_weapnext');
1707 g_Console_BindKey(e_JoyButtonToKey(i, 4), '+p' + IntToStr(i mod 2 + 1) + '_weapprev');
1708 g_Console_BindKey(e_JoyButtonToKey(i, 7), '+p' + IntToStr(i mod 2 + 1) + '_strafe');
1709 end;
1711 g_Console_BindKey(VK_LEFT, '+moveleft');
1712 g_Console_BindKey(VK_RIGHT, '+moveright');
1713 g_Console_BindKey(VK_UP, '+lookup');
1714 g_Console_BindKey(VK_DOWN, '+lookdown');
1715 g_Console_BindKey(VK_JUMP, '+jump');
1716 g_Console_BindKey(VK_FIRE, '+attack');
1717 g_Console_BindKey(VK_OPEN, '+activate');
1718 g_Console_BindKey(VK_NEXT, '+weapnext');
1719 g_Console_BindKey(VK_PREV, '+weapprev');
1720 g_Console_BindKey(VK_STRAFE, '+strafe');
1721 g_Console_BindKey(VK_0, 'weapon 1');
1722 g_Console_BindKey(VK_1, 'weapon 2');
1723 g_Console_BindKey(VK_2, 'weapon 3');
1724 g_Console_BindKey(VK_3, 'weapon 4');
1725 g_Console_BindKey(VK_4, 'weapon 5');
1726 g_Console_BindKey(VK_5, 'weapon 6');
1727 g_Console_BindKey(VK_6, 'weapon 7');
1728 g_Console_BindKey(VK_7, 'weapon 8');
1729 g_Console_BindKey(VK_8, 'weapon 9');
1730 g_Console_BindKey(VK_9, 'weapon 10');
1731 g_Console_BindKey(VK_A, 'weapon 11');
1732 g_Console_BindKey(VK_CHAT, 'togglechat');
1733 g_Console_BindKey(VK_TEAM, 'toggleteamchat');
1734 g_Console_BindKey(VK_PRINTSCR, 'screenshot');
1735 g_Console_BindKey(VK_STATUS, '+scores');
1737 // VK_CONSOLE
1738 // VK_ESCAPE
1739 end;
1741 procedure g_Console_ReadConfig (filename: String);
1742 var f: TextFile; s: AnsiString; i, len: Integer;
1743 begin
1744 if FileExists(filename) then
1745 begin
1746 AssignFile(f, filename);
1747 Reset(f);
1748 while not EOF(f) do
1749 begin
1750 ReadLn(f, s);
1751 len := Length(s);
1752 if len > 0 then
1753 begin
1754 i := 1;
1755 (* skip spaces *)
1756 while (i <= len) and (s[i] <= ' ') do inc(i);
1757 (* skip comments *)
1758 if (i <= len) and ((s[i] <> '#') and ((i + 1 > len) or (s[i] <> '/') or (s[i + 1] <> '/'))) then
1759 g_Console_Process(s, True)
1760 end
1761 end;
1762 CloseFile(f)
1763 end
1764 end;
1766 procedure g_Console_WriteConfig (filename: String);
1767 var f: TextFile; i, j: Integer; act: AnsiString;
1768 begin
1769 AssignFile(f, filename);
1770 Rewrite(f);
1771 WriteLn(f, '// generated by doom2d, do not modify');
1772 WriteLn(f, 'unbindall');
1773 for i := 0 to e_MaxInputKeys - 1 do
1774 begin
1775 if Length(gInputBinds[i].commands) > 0 then
1776 begin
1777 act := gInputBinds[i].commands[0];
1778 for j := 1 to High(gInputBinds[i].commands) do
1779 act := act + '; ' + gInputBinds[i].commands[j];
1780 WriteLn(f, 'bind "', LowerCase(e_KeyNames[i]), '" "', act, '"')
1781 end
1782 end;
1783 for i := 0 to High(commands) do
1784 begin
1785 if not commands[i].cheat then
1786 begin
1787 if @commands[i].procEx = @boolVarHandler then
1788 begin
1789 if PBoolean(commands[i].ptr)^ then j := 1 else j := 0;
1790 WriteLn(f, commands[i].cmd, ' "', j, '"')
1791 end
1792 else if @commands[i].procEx = @intVarHandler then
1793 begin
1794 WriteLn(f, commands[i].cmd, ' "', PInteger(commands[i].ptr)^, '"')
1795 end
1796 else if @commands[i].procEx = @singleVarHandler then
1797 begin
1798 WriteLn(f, commands[i].cmd, ' "', PVarSingle(commands[i].ptr).val^:0:6, '"')
1799 end
1800 end
1801 end;
1802 CloseFile(f)
1803 end;
1806 end.