hedgewars/uChat.pas
branchsdl2transition
changeset 11362 ed5a6478e710
parent 11360 7a7611adf715
parent 11317 62287d4044e7
child 11363 9006e158a81f
equal deleted inserted replaced
11361:31570b766315 11362:ed5a6478e710
     1 (*
     1 (*
     2  * Hedgewars, a free turn based strategy game
     2  * Hedgewars, a free turn based strategy game
     3  * Copyright (c) 2004-2013 Andrey Korotaev <unC0Rr@gmail.com>
     3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
     4  *
     4  *
     5  * This program is free software; you can redistribute it and/or modify
     5  * This program is free software; you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation; version 2 of the License
     7  * the Free Software Foundation; version 2 of the License
     8  *
     8  *
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    12  * GNU General Public License for more details.
    13  *
    13  *
    14  * You should have received a copy of the GNU General Public License
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program; if not, write to the Free Software
    15  * along with this program; if not, write to the Free Software
    16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
    16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    17  *)
    17  *)
    18 
    18 
    19 {$INCLUDE "options.inc"}
    19 {$INCLUDE "options.inc"}
    20 
    20 
    21 unit uChat;
    21 unit uChat;
    27 procedure freeModule;
    27 procedure freeModule;
    28 procedure ReloadLines;
    28 procedure ReloadLines;
    29 procedure CleanupInput;
    29 procedure CleanupInput;
    30 procedure AddChatString(s: shortstring);
    30 procedure AddChatString(s: shortstring);
    31 procedure DrawChat;
    31 procedure DrawChat;
       
    32 procedure KeyPressChat(Key, Sym: Longword; Modifier: Word);
    32 procedure SendHogSpeech(s: shortstring);
    33 procedure SendHogSpeech(s: shortstring);
    33 
    34 procedure CopyToClipboard(var newContent: shortstring);
    34 procedure KeyPressChat(Sym: Longword);
    35 
    35 procedure TextInput(var event: TSDL_TextInputEvent);
    36 procedure TextInput(var event: TSDL_TextInputEvent);
    36 
    37 
    37 implementation
    38 implementation
    38 uses uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO;
    39 uses uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils;
    39 
    40 
    40 const MaxStrIndex = 27;
    41 const MaxStrIndex = 27;
       
    42       MaxInputStrLen = 200;
    41 
    43 
    42 type TChatLine = record
    44 type TChatLine = record
    43     Tex: PTexture;
    45     Tex: PTexture;
    44     Time: Longword;
    46     Time: Longword;
    45     Width: LongInt;
    47     Width: LongInt;
    46     s: shortstring;
    48     s: shortstring;
       
    49     Color: TSDL_Color;
    47     end;
    50     end;
    48     TChatCmd = (quit, pause, finish, showhistory, fullscreen);
    51     TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen);
    49 
    52 
    50 var Strs: array[0 .. MaxStrIndex] of TChatLine;
    53 var Strs: array[0 .. MaxStrIndex] of TChatLine;
    51     MStrs: array[0 .. MaxStrIndex] of shortstring;
    54     MStrs: array[0 .. MaxStrIndex] of shortstring;
    52     LocalStrs: array[0 .. MaxStrIndex] of shortstring;
    55     LocalStrs: array[0 .. MaxStrIndex] of shortstring;
    53     missedCount: LongWord;
    56     missedCount: LongWord;
    54     lastStr: LongWord;
    57     lastStr: LongWord;
    55     localLastStr: LongInt;
    58     localLastStr: LongInt;
    56     history: LongInt;
    59     history: LongInt;
    57     visibleCount: LongWord;
    60     visibleCount: LongWord;
    58     InputStr: TChatLine;
    61     InputStr: TChatLine;
    59     InputStrL: array[0..260] of char; // for full str + 4-byte utf-8 char
       
    60     ChatReady: boolean;
    62     ChatReady: boolean;
    61     showAll: boolean;
    63     showAll: boolean;
       
    64     liveLua: boolean;
       
    65     ChatHidden: boolean;
       
    66     firstDraw: boolean;
       
    67     InputLinePrefix: TChatLine;
       
    68     // cursor
       
    69     cursorPos, cursorX, selectedPos, selectionDx: LongInt;
       
    70     LastKeyPressTick: LongWord;
       
    71 
    62 
    72 
    63 const
    73 const
    64     colors: array[#0..#6] of TSDL_Color = (
    74     colors: array[#0..#9] of TSDL_Color = (
    65             (r:$FF; g:$FF; b:$FF; a:$FF), // unused, feel free to take it for anything
    75             (r:$FF; g:$FF; b:$FF; a:$FF), // #0 unused, feel free to take it for anything
    66             (r:$FF; g:$FF; b:$FF; a:$FF), // chat message [White]
    76             (r:$FF; g:$FF; b:$FF; a:$FF), // #1 chat message [White]
    67             (r:$FF; g:$00; b:$FF; a:$FF), // action message [Purple]
    77             (r:$FF; g:$00; b:$FF; a:$FF), // #2 action message [Purple]
    68             (r:$90; g:$FF; b:$90; a:$FF), // join/leave message [Lime]
    78             (r:$90; g:$FF; b:$90; a:$FF), // #3 join/leave message [Lime]
    69             (r:$FF; g:$FF; b:$A0; a:$FF), // team message [Light Yellow]
    79             (r:$FF; g:$FF; b:$A0; a:$FF), // #4 team message [Light Yellow]
    70             (r:$FF; g:$00; b:$00; a:$FF), // error messages [Red]
    80             (r:$FF; g:$00; b:$00; a:$FF), // #5 error messages [Red]
    71             (r:$00; g:$FF; b:$FF; a:$FF)  // input line [Light Blue]
    81             (r:$00; g:$FF; b:$FF; a:$FF), // #6 input line [Light Blue]
       
    82             (r:$FF; g:$80; b:$80; a:$FF), // #7 team gone [Light Red]
       
    83             (r:$FF; g:$D0; b:$80; a:$FF), // #8 team back [Light Orange]
       
    84             (r:$DF; g:$DF; b:$DF; a:$FF)  // #9 hog speech [Light Gray]
    72             );
    85             );
    73     ChatCommandz: array [TChatCmd] of record
    86     ChatCommandz: array [TChatCmd] of record
    74             ChatCmd: string[31];
    87             ChatCmd: string[31];
    75             ProcedureCallChatCmd: string[31];
    88             ProcedureCallChatCmd: string[31];
    76             end = (
    89             end = (
    79             (ChatCmd: '/finish'; ProcedureCallChatCmd: 'finish'),
    92             (ChatCmd: '/finish'; ProcedureCallChatCmd: 'finish'),
    80             (ChatCmd: '/history'; ProcedureCallChatCmd: 'history'),
    93             (ChatCmd: '/history'; ProcedureCallChatCmd: 'history'),
    81             (ChatCmd: '/fullscreen'; ProcedureCallChatCmd: 'fullscr')
    94             (ChatCmd: '/fullscreen'; ProcedureCallChatCmd: 'fullscr')
    82             );
    95             );
    83 
    96 
       
    97 
       
    98 const Padding  = 2;
       
    99       ClHeight = 2 * Padding + 16; // font height
       
   100 
       
   101 // relevant for UTF-8 handling
       
   102 function IsFirstCharByte(c: char): boolean; inline;
       
   103 begin
       
   104     // based on https://en.wikipedia.org/wiki/UTF-8#Description
       
   105     IsFirstCharByte:= (byte(c) and $C0) <> $80;
       
   106 end;
       
   107 
       
   108 function charIsForHogSpeech(c: char): boolean;
       
   109 begin
       
   110 exit((c = '"') or (c = '''') or (c = '-'));
       
   111 end;
       
   112 
       
   113 procedure ResetSelection();
       
   114 begin
       
   115     selectedPos:= -1;
       
   116 end;
       
   117 
       
   118 procedure UpdateCursorCoords();
       
   119 var font: THWFont;
       
   120     str : shortstring;
       
   121     coff, soff: LongInt;
       
   122 begin
       
   123     if cursorPos = selectedPos then
       
   124         ResetSelection();
       
   125 
       
   126     // calculate cursor offset
       
   127 
       
   128     str:= InputStr.s;
       
   129     font:= CheckCJKFont(ansistring(str), fnt16);
       
   130 
       
   131     // get only substring before cursor to determine length
       
   132     // SetLength(str, cursorPos); // makes pas2c unhappy
       
   133     str[0]:= char(cursorPos);
       
   134     // get render size of text
       
   135     TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @coff, nil);
       
   136 
       
   137     cursorX:= 2 + coff;
       
   138 
       
   139     // calculate selection width on screen
       
   140     if selectedPos >= 0 then
       
   141         begin
       
   142         if selectedPos > cursorPos then
       
   143             str:= InputStr.s;
       
   144         // SetLength(str, selectedPos); // makes pas2c unhappy
       
   145         str[0]:= char(selectedPos);
       
   146         TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @soff, nil);
       
   147         selectionDx:= soff - coff;
       
   148         end
       
   149     else
       
   150         selectionDx:= 0;
       
   151 end;
       
   152 
       
   153 
       
   154 procedure ResetCursor();
       
   155 begin
       
   156     ResetSelection();
       
   157     cursorPos:= 0;
       
   158     UpdateCursorCoords();
       
   159 end;
       
   160 
       
   161 (* This procedure [re]renders a texture showing str for the chat line cl.
       
   162  * It will use the color stored in cl and update width
       
   163  *)
       
   164 procedure RenderChatLineTex(var cl: TChatLine; var str: shortstring);
       
   165 var strSurface,
       
   166     resSurface: PSDL_Surface;
       
   167     dstrect   : TSDL_Rect; // destination rectangle for blitting
       
   168     font      : THWFont;
       
   169 const
       
   170     shadowint  = $80 shl AShift;
       
   171 begin
       
   172 
       
   173 FreeAndNilTexture(cl.Tex);
       
   174 
       
   175 font:= CheckCJKFont(ansistring(str), fnt16);
       
   176 
       
   177 // get render size of text
       
   178 TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @cl.Width, nil);
       
   179 
       
   180 // calculate and save size
       
   181 cl.Width := cl.Width  + 2 * Padding;
       
   182 
       
   183 // create surface to draw on
       
   184 resSurface:= SDL_CreateRGBSurface(
       
   185                 0, toPowerOf2(cl.Width), toPowerOf2(ClHeight),
       
   186                 32, RMask, GMask, BMask, AMask);
       
   187 
       
   188 // define area we want to draw in
       
   189 dstrect.x:= 0;
       
   190 dstrect.y:= 0;
       
   191 dstrect.w:= cl.Width;
       
   192 dstrect.h:= ClHeight;
       
   193 
       
   194 // draw background
       
   195 SDL_FillRect(resSurface, @dstrect, shadowint);
       
   196 
       
   197 // create and blit text
       
   198 strSurface:= TTF_RenderUTF8_Blended(Fontz[font].Handle, Str2PChar(str), cl.color);
       
   199 //SDL_UpperBlit(strSurface, nil, resSurface, @dstrect);
       
   200 if strSurface <> nil then copyTOXY(strSurface, resSurface, Padding, Padding);
       
   201 SDL_FreeSurface(strSurface);
       
   202 
       
   203 cl.Tex:= Surface2Tex(resSurface, false);
       
   204 
       
   205 SDL_FreeSurface(resSurface)
       
   206 end;
       
   207 
       
   208 const ClDisplayDuration = 12500;
       
   209 
    84 procedure SetLine(var cl: TChatLine; str: shortstring; isInput: boolean);
   210 procedure SetLine(var cl: TChatLine; str: shortstring; isInput: boolean);
    85 var strSurface, resSurface: PSDL_Surface;
   211 var color  : TSDL_Color;
    86     w, h: LongInt;
   212 begin
    87     color: TSDL_Color;
       
    88     font: THWFont;
       
    89 begin
       
    90 if cl.Tex <> nil then
       
    91     FreeTexture(cl.Tex);
       
    92 
       
    93 cl.s:= str;
       
    94 
       
    95 if isInput then
   213 if isInput then
    96     begin
   214     begin
       
   215     cl.s:= str;
    97     color:= colors[#6];
   216     color:= colors[#6];
    98     str:= UserNick + '> ' + str + '_'
   217     str:= str + ' ';
    99     end
   218     end
   100 else
   219 else
   101     begin
   220     begin
   102     color:= colors[str[1]];
   221     if str[1] <= High(colors) then
   103     delete(str, 1, 1)
   222         begin
       
   223         color:= colors[str[1]];
       
   224         delete(str, 1, 1);
       
   225         end
       
   226     // fallback if invalid color
       
   227     else
       
   228         color:= colors[Low(colors)];
       
   229 
       
   230     cl.s:= str;
   104     end;
   231     end;
   105 
   232 
   106 font:= CheckCJKFont(str, fnt16);
   233 cl.color:= color;
   107 w:= 0; h:= 0; // avoid compiler hints
   234 
   108 TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(str), @w, @h);
   235 // set texture, note: variables cl.s and str will be different here if isInput
   109 
   236 RenderChatLineTex(cl, str);
   110 resSurface:= SDL_CreateRGBSurface(0, toPowerOf2(w), toPowerOf2(h), 32, RMask, GMask, BMask, AMask);
   237 
   111 
   238 cl.Time:= RealTicks + ClDisplayDuration;
   112 strSurface:= TTF_RenderUTF8_Solid(Fontz[font].Handle, Str2PChar(str), color);
       
   113 cl.Width:= w + 4;
       
   114 SDL_UpperBlit(strSurface, nil, resSurface, nil);
       
   115 SDL_FreeSurface(strSurface);
       
   116 
       
   117 cl.Time:= RealTicks + 12500;
       
   118 cl.Tex:= Surface2Tex(resSurface, false);
       
   119 
       
   120 SDL_FreeSurface(resSurface)
       
   121 end;
   239 end;
   122 
   240 
   123 // For uStore texture recreation
   241 // For uStore texture recreation
   124 procedure ReloadLines;
   242 procedure ReloadLines;
   125 var i, t: LongWord;
   243 var i: LongWord;
   126 begin
   244 begin
   127     if InputStr.s <> '' then
   245     if InputStr.s <> '' then
   128         SetLine(InputStr, InputStr.s, true);
   246         SetLine(InputStr, InputStr.s, true);
   129     for i:= 0 to MaxStrIndex do
   247     for i:= 0 to MaxStrIndex do
   130         if Strs[i].s <> '' then
   248         if Strs[i].s <> '' then
   131             begin
   249             begin
   132             t:= Strs[i].Time;
   250             RenderChatLineTex(Strs[i], Strs[i].s);
   133             SetLine(Strs[i], Strs[i].s, false);
       
   134             Strs[i].Time:= t
       
   135             end;
   251             end;
   136 end;
   252 end;
   137 
   253 
   138 procedure AddChatString(s: shortstring);
   254 procedure AddChatString(s: shortstring);
   139 begin
   255 begin
   152 SetLine(Strs[lastStr], s, false);
   268 SetLine(Strs[lastStr], s, false);
   153 
   269 
   154 inc(visibleCount)
   270 inc(visibleCount)
   155 end;
   271 end;
   156 
   272 
       
   273 procedure CheckPasteBuffer(); forward;
       
   274 
       
   275 procedure UpdateInputLinePrefix();
       
   276 begin
       
   277 if liveLua then
       
   278     begin
       
   279     InputLinePrefix.color:= colors[#1];
       
   280     InputLinePrefix.s:= '[Lua] >';
       
   281     end
       
   282 else
       
   283     begin
       
   284     InputLinePrefix.color:= colors[#6];
       
   285     InputLinePrefix.s:= UserNick + '>';
       
   286     end;
       
   287 
       
   288 FreeAndNilTexture(InputLinePrefix.Tex);
       
   289 end;
       
   290 
   157 procedure DrawChat;
   291 procedure DrawChat;
   158 var i, t, cnt: Longword;
   292 var i, t, left, top, cnt: LongInt;
   159     r: TSDL_Rect;
   293     selRect: TSDL_Rect;
       
   294     c: char;
   160 begin
   295 begin
   161 ChatReady:= true; // maybe move to somewhere else?
   296 ChatReady:= true; // maybe move to somewhere else?
   162 if MissedCount <> 0 then // there are chat strings we missed, so print them now
   297 
   163     begin
   298 if ChatHidden and (not showAll) then
   164     for i:= 0 to MissedCount - 1 do
   299     visibleCount:= 0;
   165         AddChatString(MStrs[i]);
   300 
   166     MissedCount:= 0;
   301 // draw chat lines with some distance from screen border
       
   302 left:= 4 - cScreenWidth div 2;
       
   303 top := 10 + visibleCount * ClHeight; // we start with input line (if any)
       
   304 
       
   305 // draw chat input line first and under all other lines
       
   306 if (GameState = gsChat) and (InputStr.Tex <> nil) then
       
   307     begin
       
   308     CheckPasteBuffer();
       
   309 
       
   310     if InputLinePrefix.Tex = nil then
       
   311         RenderChatLineTex(InputLinePrefix, InputLinePrefix.s);
       
   312 
       
   313     DrawTexture(left, top, InputLinePrefix.Tex);
       
   314     inc(left, InputLinePrefix.Width);
       
   315     DrawTexture(left, top, InputStr.Tex);
       
   316 
       
   317     if firstDraw then
       
   318         begin
       
   319         UpdateCursorCoords();
       
   320         firstDraw:= false;
       
   321         end;
       
   322 
       
   323     if selectedPos < 0 then
       
   324         begin
       
   325         // draw cursor
       
   326         if ((RealTicks - LastKeyPressTick) and 512) < 256 then
       
   327             DrawLineOnScreen(left + cursorX, top + 2, left + cursorX, top + ClHeight - 2, 2.0, $00, $FF, $FF, $FF);
       
   328         end
       
   329     else // draw selection
       
   330         begin
       
   331         selRect.y:= top + 2;
       
   332         selRect.h:= clHeight - 4;
       
   333         if selectionDx < 0 then
       
   334             begin
       
   335             selRect.x:= left + cursorX + selectionDx;
       
   336             selRect.w:= -selectionDx;
       
   337             end
       
   338         else
       
   339             begin
       
   340             selRect.x:= left + cursorX;
       
   341             selRect.w:= selectionDx;
       
   342             end;
       
   343 
       
   344         DrawRect(selRect, $FF, $FF, $FF, $40, true);
       
   345         end;
       
   346 
       
   347     dec(left, InputLinePrefix.Width);
       
   348 
       
   349 
       
   350     if (Length(InputStr.s) > 0) and ((CursorPos = 1) or (CursorPos = 2)) then
       
   351         begin
       
   352         c:= InputStr.s[1];
       
   353         if charIsForHogSpeech(c) then
       
   354             begin
       
   355             SpeechHogNumber:= 0;
       
   356             if Length(InputStr.s) > 1 then
       
   357                 begin
       
   358                 c:= InputStr.s[2];
       
   359                 if (c > '0') and (c < '9') then
       
   360                     SpeechHogNumber:= byte(c) - 48;
       
   361                 end;
       
   362             // default to current hedgehog (if own) or first hedgehog
       
   363             if SpeechHogNumber = 0 then
       
   364                 begin
       
   365                 if not CurrentTeam^.ExtDriven then
       
   366                     SpeechHogNumber:= CurrentTeam^.CurrHedgehog + 1
       
   367                 else
       
   368                     SpeechHogNumber:= 1;
       
   369                 end;
       
   370             end;
       
   371         end
       
   372     else
       
   373         SpeechHogNumber:= -1;
       
   374     end
       
   375 else
       
   376     SpeechHogNumber:= -1;
       
   377 
       
   378 // draw chat lines
       
   379 if ((not ChatHidden) or showAll) and (UIDisplay <> uiNone) then
       
   380     begin
       
   381     if MissedCount <> 0 then // there are chat strings we missed, so print them now
       
   382         begin
       
   383         for i:= 0 to MissedCount - 1 do
       
   384             AddChatString(MStrs[i]);
       
   385         MissedCount:= 0;
       
   386         end;
       
   387     i:= lastStr;
       
   388 
       
   389     cnt:= 0; // count of lines displayed
       
   390     t  := 1; // # of current line processed
       
   391 
       
   392     // draw lines in reverse order
       
   393     while (((t < 7) and (Strs[i].Time > RealTicks)) or ((t <= MaxStrIndex + 1) and showAll))
       
   394     and (Strs[i].Tex <> nil) do
       
   395         begin
       
   396         top:= top - ClHeight;
       
   397         // draw chatline only if not offscreen
       
   398         if top > 0 then
       
   399             DrawTexture(left, top, Strs[i].Tex);
       
   400 
       
   401         if i = 0 then
       
   402             i:= MaxStrIndex
       
   403         else
       
   404             dec(i);
       
   405 
       
   406         inc(cnt);
       
   407         inc(t)
       
   408         end;
       
   409 
       
   410     visibleCount:= cnt;
   167     end;
   411     end;
   168 cnt:= 0;
       
   169 t:= 0;
       
   170 i:= lastStr;
       
   171 
       
   172 r.x:= 6 - cScreenWidth div 2;
       
   173 r.y:= (visibleCount - t) * 16 + 10;
       
   174 r.h:= 16;
       
   175 
       
   176 if (GameState = gsChat) and (InputStr.Tex <> nil) then
       
   177     begin
       
   178     r.w:= InputStr.Width;
       
   179     DrawFillRect(r);
       
   180     Tint($00, $00, $00, $80);
       
   181     DrawTexture(9 - cScreenWidth div 2, visibleCount * 16 + 11, InputStr.Tex);
       
   182     untint;
       
   183     DrawTexture(8 - cScreenWidth div 2, visibleCount * 16 + 10, InputStr.Tex);
       
   184     end;
       
   185 
       
   186 dec(r.y, 16);
       
   187 
       
   188 while (((t < 7) and (Strs[i].Time > RealTicks)) or ((t < MaxStrIndex) and showAll))
       
   189 and (Strs[i].Tex <> nil) do
       
   190     begin
       
   191     r.w:= Strs[i].Width;
       
   192     DrawFillRect(r);
       
   193     Tint($00, $00, $00, $80);
       
   194     DrawTexture(9 - cScreenWidth div 2, (visibleCount - t) * 16 - 5, Strs[i].Tex);
       
   195     untint;
       
   196     DrawTexture(8 - cScreenWidth div 2, (visibleCount - t) * 16 - 6, Strs[i].Tex);
       
   197     dec(r.y, 16);
       
   198 
       
   199     if i = 0 then
       
   200         i:= MaxStrIndex
       
   201     else
       
   202         dec(i);
       
   203 
       
   204     inc(cnt);
       
   205     inc(t)
       
   206     end;
       
   207 
       
   208 visibleCount:= cnt;
       
   209 end;
   412 end;
   210 
   413 
   211 procedure SendHogSpeech(s: shortstring);
   414 procedure SendHogSpeech(s: shortstring);
   212 begin
   415 begin
   213 SendIPC('h' + s);
   416 SendIPC('h' + s);
   214 ParseCommand('/hogsay '+s, true)
   417 ParseCommand('/hogsay '+s, true)
       
   418 end;
       
   419 
       
   420 procedure SendConsoleCommand(s: shortstring);
       
   421 begin
       
   422     Delete(s, 1, 1);
       
   423     SendIPC('~' + s)
   215 end;
   424 end;
   216 
   425 
   217 procedure AcceptChatString(s: shortstring);
   426 procedure AcceptChatString(s: shortstring);
   218 var i: TWave;
   427 var i: TWave;
   219     j: TChatCmd;
   428     j: TChatCmd;
   220     c, t: LongInt;
   429     c, t: LongInt;
   221     x: byte;
   430     x: byte;
   222 begin
   431 begin
       
   432 if s <> LocalStrs[localLastStr] then
       
   433     begin
       
   434     // put in input history
       
   435     localLastStr:= (localLastStr + 1) mod MaxStrIndex;
       
   436     LocalStrs[localLastStr]:= s;
       
   437     end;
       
   438 
   223 t:= LocalTeam;
   439 t:= LocalTeam;
   224 x:= 0;
   440 x:= 0;
   225 if (s[1] = '"') and (s[Length(s)] = '"')
   441 if (s[1] = '"') and (s[Length(s)] = '"')
   226     then x:= 1
   442     then x:= 1
   227 
   443 
   229     x:= 2
   445     x:= 2
   230 
   446 
   231 else if (s[1] = '-') and (s[Length(s)] = '-') then
   447 else if (s[1] = '-') and (s[Length(s)] = '-') then
   232     x:= 3;
   448     x:= 3;
   233 
   449 
   234 if not CurrentTeam^.ExtDriven and (x <> 0) then
   450 if (not CurrentTeam^.ExtDriven) and (x <> 0) then
   235     for c:= 0 to Pred(TeamsCount) do
   451     for c:= 0 to Pred(TeamsCount) do
   236         if (TeamsArray[c] = CurrentTeam) then
   452         if (TeamsArray[c] = CurrentTeam) then
   237             t:= c;
   453             t:= c;
   238 
   454 
   239 if x <> 0 then
   455 if x <> 0 then
   246     end;
   462     end;
   247 
   463 
   248 if (s[1] = '/') then
   464 if (s[1] = '/') then
   249     begin
   465     begin
   250     // These 3 are same as above, only are to make the hedgehog say it on next attack
   466     // These 3 are same as above, only are to make the hedgehog say it on next attack
   251     if (copy(s, 1, 5) = '/hsa ') then
   467     if (copy(s, 2, 4) = 'hsa ') then
   252         begin
   468         begin
   253         if CurrentTeam^.ExtDriven then
   469         if CurrentTeam^.ExtDriven then
   254             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   470             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   255         else
   471         else
   256             SendHogSpeech(#4 + copy(s, 6, Length(s)-5));
   472             SendHogSpeech(#4 + copy(s, 6, Length(s)-5));
   257         exit
   473         exit
   258         end;
   474         end;
   259 
   475 
   260     if (copy(s, 1, 5) = '/hta ') then
   476     if (copy(s, 2, 4) = 'hta ') then
   261         begin
   477         begin
   262         if CurrentTeam^.ExtDriven then
   478         if CurrentTeam^.ExtDriven then
   263             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   479             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   264         else
   480         else
   265             SendHogSpeech(#5 + copy(s, 6, Length(s)-5));
   481             SendHogSpeech(#5 + copy(s, 6, Length(s)-5));
   266         exit
   482         exit
   267         end;
   483         end;
   268 
   484 
   269     if (copy(s, 1, 5) = '/hya ') then
   485     if (copy(s, 2, 4) = 'hya ') then
   270         begin
   486         begin
   271         if CurrentTeam^.ExtDriven then
   487         if CurrentTeam^.ExtDriven then
   272             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   488             ParseCommand('/say ' + copy(s, 6, Length(s)-5), true)
   273         else
   489         else
   274             SendHogSpeech(#6 + copy(s, 6, Length(s)-5));
   490             SendHogSpeech(#6 + copy(s, 6, Length(s)-5));
   275         exit
   491         exit
   276         end;
   492         end;
   277 
   493 
   278     if (copy(s, 1, 6) = '/team ') and (length(s) > 6) then
   494     if (copy(s, 2, 5) = 'team ') and (length(s) > 6) then
   279         begin
   495         begin
   280         ParseCommand(s, true);
   496         ParseCommand(s, true);
   281         exit
   497         exit
   282         end;
   498         end;
   283 
   499 
   284     if (copy(s, 1, 4) = '/me ') then
   500     if (copy(s, 2, 3) = 'me ') then
   285         begin
   501         begin
   286         ParseCommand('/say ' + s, true);
   502         ParseCommand('/say ' + s, true);
   287         exit
   503         exit
   288         end;
   504         end;
   289 
   505 
       
   506     if (copy(s, 2, 10) = 'togglechat') then
       
   507         begin
       
   508         ChatHidden:= (not ChatHidden);
       
   509         if ChatHidden then
       
   510            showAll:= false;
       
   511         exit
       
   512         end;
       
   513 
       
   514     // debugging commands
       
   515     if (copy(s, 2, 7) = 'debugvl') then
       
   516         begin
       
   517         cViewLimitsDebug:= (not cViewLimitsDebug);
       
   518         UpdateViewLimits();
       
   519         exit
       
   520         end;
       
   521 
       
   522     if (copy(s, 2, 3) = 'lua') then
       
   523         begin
       
   524         AddFileLog('/lua issued');
       
   525         if gameType <> gmtNet then
       
   526             begin
       
   527             liveLua:= (not liveLua);
       
   528             if liveLua then
       
   529                 begin
       
   530                 AddFileLog('[Lua] chat input string parsing enabled');
       
   531                 AddChatString(#3 + 'Lua parsing: ON');
       
   532                 end
       
   533             else
       
   534                 begin
       
   535                 AddFileLog('[Lua] chat input string parsing disabled');
       
   536                 AddChatString(#3 + 'Lua parsing: OFF');
       
   537                 end;
       
   538             UpdateInputLinePrefix();
       
   539             end;
       
   540         exit
       
   541         end;
       
   542 
       
   543     // hedghog animations/taunts and engine commands
   290     if (not CurrentTeam^.ExtDriven) and (CurrentTeam^.Hedgehogs[0].BotLevel = 0) then
   544     if (not CurrentTeam^.ExtDriven) and (CurrentTeam^.Hedgehogs[0].BotLevel = 0) then
   291         begin
   545         begin
   292         for i:= Low(TWave) to High(TWave) do
   546         for i:= Low(TWave) to High(TWave) do
   293             if (s = Wavez[i].cmd) then
   547             if (s = Wavez[i].cmd) then
   294                 begin
   548                 begin
   295                 ParseCommand('/taunt ' + char(i), true);
   549                 ParseCommand('/taunt ' + char(i), true);
   296                 exit
   550                 exit
   297                 end;
   551                 end;
   298 
   552         end;
   299         for j:= Low(TChatCmd) to High(TChatCmd) do
   553 
   300             if (s = ChatCommandz[j].ChatCmd) then
   554     for j:= Low(TChatCmd) to High(TChatCmd) do
   301                 begin
   555         if (s = ChatCommandz[j].ChatCmd) then
   302                 ParseCommand(ChatCommandz[j].ProcedureCallChatCmd, true);
   556             begin
   303                 exit
   557             ParseCommand(ChatCommandz[j].ProcedureCallChatCmd, true);
   304                 end;
   558             exit
   305         end
   559             end;
       
   560 
       
   561     if (gameType = gmtNet) then
       
   562         SendConsoleCommand(s)
   306     end
   563     end
       
   564 else
       
   565     begin
       
   566     if liveLua then
       
   567         LuaParseString(s)
   307     else
   568     else
   308         ParseCommand('/say ' + s, true);
   569         ParseCommand('/say ' + s, true);
       
   570     end;
   309 end;
   571 end;
   310 
   572 
   311 procedure CleanupInput;
   573 procedure CleanupInput;
   312 begin
   574 begin
   313     FreezeEnterKey;
   575     FreezeEnterKey;
   315     SDL_StopTextInput();
   577     SDL_StopTextInput();
   316     GameState:= gsGame;
   578     GameState:= gsGame;
   317     ResetKbd;
   579     ResetKbd;
   318 end;
   580 end;
   319 
   581 
       
   582 procedure DelBytesFromInputStrBack(endIdx: integer; count: byte);
       
   583 var startIdx: integer;
       
   584 begin
       
   585     // nothing to do if count is 0
       
   586     if count = 0 then
       
   587         exit;
       
   588 
       
   589     // first byte to delete
       
   590     startIdx:= endIdx - (count - 1);
       
   591 
       
   592     // delete bytes from string
       
   593     Delete(InputStr.s, startIdx, count);
       
   594 
       
   595     SetLine(InputStr, InputStr.s, true);
       
   596 end;
       
   597 
       
   598 procedure MoveCursorToPreviousChar();
       
   599 begin
       
   600     if cursorPos > 0 then
       
   601         repeat
       
   602             dec(cursorPos);
       
   603         until ((cursorPos = 0) or IsFirstCharByte(InputStr.s[cursorPos + 1]));
       
   604 end;
       
   605 
       
   606 procedure MoveCursorToNextChar();
       
   607 var len: integer;
       
   608 begin
       
   609     len:= Length(InputStr.s);
       
   610     if cursorPos < len then
       
   611         repeat
       
   612             inc(cursorPos);
       
   613         until ((cursorPos = len) or IsFirstCharByte(InputStr.s[cursorPos + 1]));
       
   614 end;
       
   615 
       
   616 procedure DeleteLastUTF8CharFromStr(var s: shortstring);
       
   617 var l: byte;
       
   618 begin
       
   619     l:= Length(s);
       
   620 
       
   621     while (l > 1) and (not IsFirstCharByte(s[l])) do
       
   622         begin
       
   623         dec(l);
       
   624         end;
       
   625 
       
   626     if l > 0 then
       
   627         dec(l);
       
   628 
       
   629     s[0]:= char(l);
       
   630 end;
       
   631 
       
   632 procedure DeleteSelected();
       
   633 begin
       
   634     if (selectedPos >= 0) and (cursorPos <> selectedPos) then
       
   635         begin
       
   636         DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos));
       
   637         cursorPos:= min(cursorPos, selectedPos);
       
   638         end;
       
   639     ResetSelection();
       
   640     UpdateCursorCoords();
       
   641 end;
       
   642 
       
   643 procedure HandleSelection(enabled: boolean);
       
   644 begin
       
   645 if enabled then
       
   646     begin
       
   647     if selectedPos < 0 then
       
   648         selectedPos:= cursorPos;
       
   649     end
       
   650 else
       
   651     ResetSelection();
       
   652 end;
       
   653 
       
   654 type TCharSkip = ( none, wspace, numalpha, special );
       
   655 
       
   656 function GetInputCharSkipClass(index: LongInt): TCharSkip;
       
   657 var  c: char;
       
   658 begin
       
   659     c:= InputStr.s[index];
       
   660 
       
   661     // non-ascii counts as letter
       
   662     if c > #127 then
       
   663         exit(numalpha);
       
   664 
       
   665     // low-ascii whitespaces and DEL
       
   666     if (c < #33) or (c = #127) then
       
   667         exit(wspace);
       
   668 
       
   669     // low-ascii special chars
       
   670     if c < #48 then
       
   671         exit(special);
       
   672 
       
   673     // digits
       
   674     if c < #58 then
       
   675         exit(numalpha);
       
   676 
       
   677     // make c upper-case
       
   678     if c > #96 then
       
   679         c:= char(byte(c) - 32);
       
   680 
       
   681     // letters
       
   682     if (c > #64) and (c < #90) then
       
   683         exit(numalpha);
       
   684 
       
   685     // remaining ascii are special chars
       
   686     exit(special);
       
   687 end;
       
   688 
       
   689 // skip from word to word, similar to Qt
       
   690 procedure SkipInputChars(skip: TCharSkip; backwards: boolean);
       
   691 begin
       
   692 if backwards then
       
   693     begin
       
   694     // skip trailing whitespace, similar to Qt
       
   695     while (skip = wspace) and (cursorPos > 0) do
       
   696         begin
       
   697         skip:= GetInputCharSkipClass(cursorPos);
       
   698         if skip = wspace then
       
   699             MoveCursorToPreviousChar();
       
   700         end;
       
   701     // skip same-type chars
       
   702     while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do
       
   703         MoveCursorToPreviousChar();
       
   704     end
       
   705 else
       
   706     begin
       
   707     // skip same-type chars
       
   708     while cursorPos < Length(InputStr.s) do
       
   709         begin
       
   710         MoveCursorToNextChar();
       
   711         if (GetInputCharSkipClass(cursorPos) <> skip) then
       
   712             begin
       
   713             MoveCursorToPreviousChar();
       
   714             break;
       
   715             end;
       
   716         end;
       
   717     // skip trailing whitespace, similar to Qt
       
   718     while cursorPos < Length(InputStr.s) do
       
   719         begin
       
   720         MoveCursorToNextChar();
       
   721         if (GetInputCharSkipClass(cursorPos) <> wspace) then
       
   722             begin
       
   723             MoveCursorToPreviousChar();
       
   724             break;
       
   725             end;
       
   726         end;
       
   727     end;
       
   728 end;
       
   729 
       
   730 procedure CopyToClipboard(var newContent: shortstring);
       
   731 begin
       
   732     SendIPC(_S'y' + copy(newContent, 1, 253) + #0);
       
   733 end;
       
   734 
       
   735 procedure CopySelectionToClipboard();
       
   736 var selection: shortstring;
       
   737 begin
       
   738     if selectedPos >= 0 then
       
   739         begin
       
   740         selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos));
       
   741         CopyToClipboard(selection);
       
   742         end;
       
   743 end;
       
   744 
       
   745 procedure InsertIntoInputStr(s: shortstring);
       
   746 var limit: integer;
       
   747 begin
       
   748     // we check limit for trailing stuff before insertion limit for a reason
       
   749     // (possible remaining space after too long UTF8-insertion has been shortened)
       
   750 
       
   751     // length limit for stuff to that will trail the insertion
       
   752     limit:= max(cursorPos, MaxInputStrLen-Length(s));
       
   753 
       
   754     while Length(InputStr.s) > limit do
       
   755         begin
       
   756         DeleteLastUTF8CharFromStr(InputStr.s);
       
   757         end;
       
   758 
       
   759     // length limit for stuff to insert
       
   760     limit:= max(0, MaxInputStrLen-cursorPos);
       
   761 
       
   762     if limit = 0 then
       
   763         s:= ''
       
   764     else while Length(s) > limit do
       
   765         begin
       
   766         DeleteLastUTF8CharFromStr(s);
       
   767         end;
       
   768 
       
   769     if Length(s) > 0 then
       
   770         begin
       
   771         // insert string truncated to safe length
       
   772         Insert(s, InputStr.s, cursorPos + 1);
       
   773 
       
   774         if Length(InputStr.s) > MaxInputStrLen then
       
   775             InputStr.s[0]:= char(MaxInputStrLen);
       
   776 
       
   777         SetLine(InputStr, InputStr.s, true);
       
   778 
       
   779         // move cursor to end of inserted string
       
   780         inc(cursorPos, Length(s));
       
   781         UpdateCursorCoords();
       
   782         end;
       
   783 end;
       
   784 
   320 procedure TextInput(var event: TSDL_TextInputEvent);
   785 procedure TextInput(var event: TSDL_TextInputEvent);
   321 var s: shortstring;
   786 var s: shortstring;
   322     l: byte;
   787     l: byte;
   323 begin
   788 begin
       
   789     DeleteSelected();
       
   790 
   324     l:= 0;
   791     l:= 0;
   325     while event.text[l] <> #0 do
   792     while event.text[l] <> #0 do
   326         begin
   793         begin
   327         s[l + 1]:= event.text[l];
   794         s[l + 1]:= event.text[l];
   328         inc(l)
   795         inc(l)
   329         end;
   796         end;
   330     s[0]:= char(l);
   797     s[0]:= char(l);
   331 
   798 
   332     if byte(InputStr.s[0]) + l > 240 then exit;
   799     if byte(InputStr.s[0]) + l > 240 then exit;
   333 
   800 
   334     InputStrL[byte(InputStr.s[0]) + l]:= InputStr.s[0];
   801     InsertIntoInputStr(s);
   335     SetLine(InputStr, InputStr.s + s, true)
   802 end;
   336 end;
   803 
   337 
   804 procedure PasteFromClipboard();
   338 procedure KeyPressChat(Sym: Longword);
   805 begin
       
   806     SendIPC(_S'Y');
       
   807 end;
       
   808 
       
   809 procedure CheckPasteBuffer();
       
   810 begin
       
   811     if Length(ChatPasteBuffer) > 0 then
       
   812         begin
       
   813         InsertIntoInputStr(ChatPasteBuffer);
       
   814         ChatPasteBuffer:= '';
       
   815         end;
       
   816 end;
       
   817 
       
   818 procedure KeyPressChat(Key, Sym: Longword; Modifier: Word);
   339 const firstByteMark: array[0..3] of byte = (0, $C0, $E0, $F0);
   819 const firstByteMark: array[0..3] of byte = (0, $C0, $E0, $F0);
       
   820       nonStateMask = (not (KMOD_NUM or KMOD_CAPS));
   340 var i, btw, index: integer;
   821 var i, btw, index: integer;
   341     utf8: shortstring;
   822     utf8: shortstring;
   342     action: boolean;
   823     action, selMode, ctrl, ctrlonly: boolean;
   343 begin
   824     skip: TCharSkip;
       
   825 begin
       
   826     LastKeyPressTick:= RealTicks;
   344     action:= true;
   827     action:= true;
       
   828 
       
   829     CheckPasteBuffer();
       
   830 
       
   831     selMode:= (modifier and (KMOD_LSHIFT or KMOD_RSHIFT)) <> 0;
       
   832     ctrl:= (modifier and (KMOD_LCTRL or KMOD_RCTRL)) <> 0;
       
   833     ctrlonly:= ctrl and ((modifier and nonStateMask and (not (KMOD_LCTRL or KMOD_RCTRL))) = 0);
       
   834     skip:= none;
       
   835 
   345     case Sym of
   836     case Sym of
   346         SDLK_BACKSPACE:
   837         SDLK_BACKSPACE:
   347             begin
   838             begin
       
   839             if selectedPos < 0 then
       
   840                 begin
       
   841                 HandleSelection(true);
       
   842 
       
   843                 // delete more if ctrl is held
       
   844                 if ctrl then
       
   845                     SkipInputChars(GetInputCharSkipClass(cursorPos), true)
       
   846                 else
       
   847                     MoveCursorToPreviousChar();
       
   848 
       
   849                 end;
       
   850 
       
   851             DeleteSelected();
       
   852             UpdateCursorCoords();
       
   853             end;
       
   854         SDLK_DELETE:
       
   855             begin
       
   856             if selectedPos < 0 then
       
   857                 begin
       
   858                 HandleSelection(true);
       
   859 
       
   860                 // delete more if ctrl is held
       
   861                 if ctrl then
       
   862                     SkipInputChars(GetInputCharSkipClass(cursorPos), false)
       
   863                 else
       
   864                     MoveCursorToNextChar();
       
   865 
       
   866                 end;
       
   867 
       
   868             DeleteSelected();
       
   869             UpdateCursorCoords();
       
   870             end;
       
   871         SDLK_ESCAPE:
       
   872             begin
   348             if Length(InputStr.s) > 0 then
   873             if Length(InputStr.s) > 0 then
   349                 begin
   874                 begin
   350                 InputStr.s[0]:= InputStrL[byte(InputStr.s[0])];
   875                 SetLine(InputStr, '', true);
   351                 SetLine(InputStr, InputStr.s, true)
   876                 ResetCursor();
   352                 end
   877                 end
   353             end;
   878             else CleanupInput
   354         SDLK_ESCAPE:
   879             end;
       
   880         SDLK_RETURN, SDLK_KP_ENTER:
   355             begin
   881             begin
   356             if Length(InputStr.s) > 0 then
   882             if Length(InputStr.s) > 0 then
   357                 SetLine(InputStr, '', true)
       
   358             else CleanupInput
       
   359             end;
       
   360         SDLK_RETURN, SDLK_KP_ENTER:
       
   361             begin
       
   362             if Length(InputStr.s) > 0 then
       
   363                 begin
   883                 begin
   364                 AcceptChatString(InputStr.s);
   884                 AcceptChatString(InputStr.s);
   365                 SetLine(InputStr, '', false)
   885                 SetLine(InputStr, '', false);
       
   886                 ResetCursor();
   366                 end;
   887                 end;
   367             CleanupInput
   888             CleanupInput
   368             end;
   889             end;
   369         SDLK_UP, SDLK_DOWN:
   890         SDLK_UP, SDLK_DOWN:
   370             begin
   891             begin
   371             if (Sym = SDLK_UP) and (history < localLastStr) then inc(history);
   892             if (Sym = SDLK_UP) and (history < localLastStr) then inc(history);
   372             if (Sym = SDLK_DOWN) and (history > 0) then dec(history);
   893             if (Sym = SDLK_DOWN) and (history > 0) then dec(history);
   373             index:= localLastStr - history + 1;
   894             index:= localLastStr - history + 1;
   374             if (index > localLastStr) then
   895             if (index > localLastStr) then
   375                  SetLine(InputStr, '', true)
   896                 begin
   376             else SetLine(InputStr, LocalStrs[index], true)
   897                 SetLine(InputStr, '', true);
   377             end;
   898                 end
   378         SDLK_RIGHT, SDLK_LEFT, SDLK_DELETE,
   899             else
   379         SDLK_HOME, SDLK_END,
   900                 begin
       
   901                 SetLine(InputStr, LocalStrs[index], true);
       
   902                 end;
       
   903             cursorPos:= Length(InputStr.s);
       
   904             ResetSelection();
       
   905             UpdateCursorCoords();
       
   906             end;
       
   907         SDLK_HOME:
       
   908             begin
       
   909             if cursorPos > 0 then
       
   910                 begin
       
   911                 HandleSelection(selMode);
       
   912                 cursorPos:= 0;
       
   913                 end
       
   914             else if (not selMode) then
       
   915                 ResetSelection();
       
   916 
       
   917             UpdateCursorCoords();
       
   918             end;
       
   919         SDLK_END:
       
   920             begin
       
   921             i:= Length(InputStr.s);
       
   922             if cursorPos < i then
       
   923                 begin
       
   924                 HandleSelection(selMode);
       
   925                 cursorPos:= i;
       
   926                 end
       
   927             else if (not selMode) then
       
   928                 ResetSelection();
       
   929 
       
   930             UpdateCursorCoords();
       
   931             end;
       
   932         SDLK_LEFT:
       
   933             begin
       
   934             if cursorPos > 0 then
       
   935                 begin
       
   936 
       
   937                 if ctrl then
       
   938                     skip:= GetInputCharSkipClass(cursorPos);
       
   939 
       
   940                 if selMode or (selectedPos < 0) then
       
   941                     begin
       
   942                     HandleSelection(selMode);
       
   943                     // go to end of previous utf8-char
       
   944                     MoveCursorToPreviousChar();
       
   945                     end
       
   946                 else // if we're leaving selection mode, jump to its left end
       
   947                     begin
       
   948                     cursorPos:= min(cursorPos, selectedPos);
       
   949                     ResetSelection();
       
   950                     end;
       
   951 
       
   952                 if ctrl then
       
   953                     SkipInputChars(skip, true);
       
   954 
       
   955                 end
       
   956             else if (not selMode) then
       
   957                 ResetSelection();
       
   958 
       
   959             UpdateCursorCoords();
       
   960             end;
       
   961         SDLK_RIGHT:
       
   962             begin
       
   963             if cursorPos < Length(InputStr.s) then
       
   964                 begin
       
   965 
       
   966                 if selMode or (selectedPos < 0) then
       
   967                     begin
       
   968                     HandleSelection(selMode);
       
   969                     MoveCursorToNextChar();
       
   970                     end
       
   971                 else // if we're leaving selection mode, jump to its right end
       
   972                     begin
       
   973                     cursorPos:= max(cursorPos, selectedPos);
       
   974                     ResetSelection();
       
   975                     end;
       
   976 
       
   977                 if ctrl then
       
   978                     SkipInputChars(GetInputCharSkipClass(cursorPos), false);
       
   979 
       
   980                 end
       
   981             else if (not selMode) then
       
   982                 ResetSelection();
       
   983 
       
   984             UpdateCursorCoords();
       
   985             end;
   380         SDLK_PAGEUP, SDLK_PAGEDOWN:
   986         SDLK_PAGEUP, SDLK_PAGEDOWN:
   381             begin
   987             begin
   382             // ignore me!!!
   988             // ignore me!!!
       
   989             end;
       
   990         SDLK_a:
       
   991             begin
       
   992             // select all
       
   993             if ctrlonly then
       
   994                 begin
       
   995                 ResetSelection();
       
   996                 cursorPos:= 0;
       
   997                 HandleSelection(true);
       
   998                 cursorPos:= Length(InputStr.s);
       
   999                 UpdateCursorCoords();
       
  1000                 end
       
  1001             else
       
  1002                 action:= false;
       
  1003             end;
       
  1004         SDLK_c:
       
  1005             begin
       
  1006             // copy
       
  1007             if ctrlonly then
       
  1008                 CopySelectionToClipboard()
       
  1009             else
       
  1010                 action:= false;
       
  1011             end;
       
  1012         SDLK_v:
       
  1013             begin
       
  1014             // paste
       
  1015             if ctrlonly then
       
  1016                 begin
       
  1017                 DeleteSelected();
       
  1018                 PasteFromClipboard();
       
  1019                 end
       
  1020             else
       
  1021                 action:= false;
       
  1022             end;
       
  1023         SDLK_x:
       
  1024             begin
       
  1025             // cut
       
  1026             if ctrlonly then
       
  1027                 begin
       
  1028                 CopySelectionToClipboard();
       
  1029                 DeleteSelected();
       
  1030                 end
       
  1031             else
       
  1032                 action:= false;
   383             end;
  1033             end;
   384         else
  1034         else
   385             action:= false;
  1035             action:= false;
   386         end;
  1036         end;
   387 
  1037 
       
  1038         // TODO: ctrl+c etc. probably won't work anymore while in text input mode
   388 end;
  1039 end;
   389 
  1040 
   390 procedure chChatMessage(var s: shortstring);
  1041 procedure chChatMessage(var s: shortstring);
   391 begin
  1042 begin
   392     AddChatString(s)
  1043     AddChatString(s)
   397     SendIPC('s' + s);
  1048     SendIPC('s' + s);
   398 
  1049 
   399     if copy(s, 1, 4) = '/me ' then
  1050     if copy(s, 1, 4) = '/me ' then
   400         s:= #2 + '* ' + UserNick + ' ' + copy(s, 5, Length(s) - 4)
  1051         s:= #2 + '* ' + UserNick + ' ' + copy(s, 5, Length(s) - 4)
   401     else
  1052     else
   402         begin
       
   403         localLastStr:= (localLastStr + 1) mod MaxStrIndex;
       
   404         LocalStrs[localLastStr]:= s;
       
   405         s:= #1 + UserNick + ': ' + s;
  1053         s:= #1 + UserNick + ': ' + s;
   406         end;
       
   407 
  1054 
   408     AddChatString(s)
  1055     AddChatString(s)
   409 end;
  1056 end;
   410 
  1057 
   411 procedure chTeamSay(var s: shortstring);
  1058 procedure chTeamSay(var s: shortstring);
   416 
  1063 
   417     AddChatString(s)
  1064     AddChatString(s)
   418 end;
  1065 end;
   419 
  1066 
   420 procedure chHistory(var s: shortstring);
  1067 procedure chHistory(var s: shortstring);
       
  1068 var i: LongInt;
   421 begin
  1069 begin
   422     s:= s; // avoid compiler hint
  1070     s:= s; // avoid compiler hint
   423     showAll:= not showAll
  1071     showAll:= not showAll;
       
  1072     // immediatly recount
       
  1073     visibleCount:= 0;
       
  1074     if showAll or (not ChatHidden) then
       
  1075         for i:= 0 to MaxStrIndex do
       
  1076             begin
       
  1077             if (Strs[i].Tex <> nil) and (showAll or (Strs[i].Time > RealTicks)) then
       
  1078                 inc(visibleCount);
       
  1079             end;
   424 end;
  1080 end;
   425 
  1081 
   426 procedure chChat(var s: shortstring);
  1082 procedure chChat(var s: shortstring);
   427 begin
  1083 begin
   428     s:= s; // avoid compiler hint
  1084     s:= s; // avoid compiler hint
   429     GameState:= gsChat;
  1085     GameState:= gsChat;
   430     SDL_StartTextInput();
  1086     SDL_StartTextInput();
   431     if length(s) = 0 then
  1087     if length(s) = 0 then
   432         SetLine(InputStr, '', true)
  1088         SetLine(InputStr, '', true)
   433     else
  1089     else
   434         SetLine(InputStr, '/team ', true)
  1090         begin
       
  1091         SetLine(InputStr, '/team ', true);
       
  1092         cursorPos:= 6;
       
  1093         UpdateCursorCoords();
       
  1094         end;
   435 end;
  1095 end;
   436 
  1096 
   437 procedure initModule;
  1097 procedure initModule;
   438 var i: ShortInt;
  1098 var i: ShortInt;
   439 begin
  1099 begin
   448     history:= 0;
  1108     history:= 0;
   449     visibleCount:= 0;
  1109     visibleCount:= 0;
   450     showAll:= false;
  1110     showAll:= false;
   451     ChatReady:= false;
  1111     ChatReady:= false;
   452     missedCount:= 0;
  1112     missedCount:= 0;
   453 
  1113     liveLua:= false;
       
  1114     ChatHidden:= false;
       
  1115     firstDraw:= true;
       
  1116 
       
  1117     InputLinePrefix.Tex:= nil;
       
  1118     UpdateInputLinePrefix();
       
  1119     inputStr.s:= '';
   454     inputStr.Tex := nil;
  1120     inputStr.Tex := nil;
   455     for i:= 0 to MaxStrIndex do
  1121     for i:= 0 to MaxStrIndex do
   456         Strs[i].Tex := nil;
  1122         Strs[i].Tex := nil;
       
  1123 
       
  1124     LastKeyPressTick:= 0;
       
  1125     ResetCursor();
   457 end;
  1126 end;
   458 
  1127 
   459 procedure freeModule;
  1128 procedure freeModule;
   460 var i: ShortInt;
  1129 var i: ShortInt;
   461 begin
  1130 begin
   462     FreeTexture(InputStr.Tex);
  1131     FreeAndNilTexture(InputLinePrefix.Tex);
       
  1132     FreeAndNilTexture(InputStr.Tex);
   463     for i:= 0 to MaxStrIndex do
  1133     for i:= 0 to MaxStrIndex do
   464         FreeTexture(Strs[i].Tex);
  1134         FreeAndNilTexture(Strs[i].Tex);
   465 end;
  1135 end;
   466 
  1136 
   467 end.
  1137 end.