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 |
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) |