DEADSOFTWARE

typo in mapcvt: microseconds -> milliseconds
[d2df-sdl.git] / src / game / g_holmes.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_holmes;
19 interface
21 uses
22 e_log, e_input,
23 g_textures, g_basic, e_graphics, g_phys, g_grid, g_player, g_monsters,
24 g_window, g_map, g_triggers, g_items, g_game, g_panel, g_console,
25 xprofiler;
28 type
29 THMouseEvent = record
30 public
31 const
32 // both for but and for bstate
33 Left = $0001;
34 Right = $0002;
35 Middle = $0004;
36 WheelUp = $0008;
37 WheelDown = $0010;
39 // event types
40 Release = 0;
41 Press = 1;
42 Motion = 2;
44 public
45 kind: Byte; // motion, press, release
46 x, y: Integer;
47 dx, dy: Integer; // for wheel this is wheel motion, otherwise this is relative mouse motion
48 but: Word; // current pressed/released button, or 0 for motion
49 bstate: Word; // button state
50 kstate: Word; // keyboard state (see THKeyEvent);
51 end;
53 THKeyEvent = record
54 public
55 const
56 // modifiers
57 ModCtrl = $0001;
58 ModAlt = $0002;
59 ModShift = $0004;
61 // event types
62 Release = 0;
63 Press = 1;
65 public
66 kind: Byte;
67 scan: Word; // SDL_SCANCODE_XXX
68 sym: Word; // SDLK_XXX
69 bstate: Word; // button state
70 kstate: Word; // keyboard state
72 public
73 end;
75 procedure g_Holmes_VidModeChanged ();
76 procedure g_Holmes_WindowFocused ();
77 procedure g_Holmes_WindowBlured ();
79 procedure g_Holmes_Draw ();
80 procedure g_Holmes_DrawUI ();
82 function g_Holmes_MouseEvent (var ev: THMouseEvent): Boolean; // returns `true` if event was eaten
83 function g_Holmes_KeyEvent (var ev: THKeyEvent): Boolean; // returns `true` if event was eaten
85 // hooks for player
86 procedure g_Holmes_plrView (viewPortX, viewPortY, viewPortW, viewPortH: Integer);
87 procedure g_Holmes_plrLaser (ax0, ay0, ax1, ay1: Integer);
90 operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
91 operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
93 operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
94 operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
97 var
98 g_holmes_enabled: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}false{$ENDIF};
101 implementation
103 uses
104 SysUtils, Classes, GL, SDL2,
105 MAPDEF, g_main, g_options, utils, hashtable, xparser;
108 var
109 //globalInited: Boolean = false;
110 msX: Integer = -666;
111 msY: Integer = -666;
112 msB: Word = 0; // button state
113 kbS: Word = 0; // keyboard modifiers state
114 showGrid: Boolean = true;
115 showMonsInfo: Boolean = false;
116 showMonsLOS2Plr: Boolean = false;
117 showAllMonsCells: Boolean = false;
118 showMapCurPos: Boolean = false;
119 showLayersWindow: Boolean = false;
120 showOutlineWindow: Boolean = false;
122 // ////////////////////////////////////////////////////////////////////////// //
123 {$INCLUDE g_holmes.inc}
124 {$INCLUDE g_holmes_ui.inc}
127 // ////////////////////////////////////////////////////////////////////////// //
128 // any mods = 255: nothing was defined
129 function parseModKeys (const s: AnsiString; out kmods: Byte; out mbuts: Byte): AnsiString;
130 var
131 pos, epos: Integer;
132 begin
133 kmods := 255;
134 mbuts := 255;
135 pos := 1;
136 //while (pos <= Length(s)) and (s[pos] <= ' ') do Inc(pos);
137 if (pos < Length(s)) and ((s[pos] = '+') or (s[pos] = '-') or (s[pos] = '*')) then Inc(pos);
138 while (pos < Length(s)) do
139 begin
140 if (Length(s)-pos >= 2) and (s[pos+1] = '-') then
141 begin
142 case s[pos] of
143 'C', 'c': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModCtrl; Inc(pos, 2); continue; end;
144 'M', 'm': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModAlt; Inc(pos, 2); continue; end;
145 'S', 's': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModShift; Inc(pos, 2); continue; end;
146 end;
147 break;
148 end;
149 if (Length(s)-pos >= 4) and ((s[pos+1] = 'M') or (s[pos+1] = 'm')) and ((s[pos+2] = 'B') or (s[pos+1] = 'b')) and (s[pos+3] = '-') then
150 begin
151 case s[pos] of
152 'L', 'l': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Left; Inc(pos, 4); continue; end;
153 'R', 'r': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Right; Inc(pos, 4); continue; end;
154 'M', 'm': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Middle; Inc(pos, 4); continue; end;
155 end;
156 break;
157 end;
158 break;
159 end;
160 epos := Length(s)+1;
161 while (epos > pos) and (s[epos-1] <= ' ') do Dec(epos);
162 if (epos > pos) then result := Copy(s, pos, epos-pos) else result := '';
163 end;
166 operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
167 var
168 f: Integer;
169 kmods: Byte = 255;
170 mbuts: Byte = 255;
171 kname: AnsiString;
172 begin
173 result := false;
174 if (Length(s) > 0) then
175 begin
176 if (s[1] = '+') then begin if (ev.kind <> ev.Press) then exit; end
177 else if (s[1] = '-') then begin if (ev.kind <> ev.Release) then exit; end
178 else if (s[1] = '*') then begin end
179 else if (ev.kind <> ev.Press) then exit;
180 end;
181 kname := parseModKeys(s, kmods, mbuts);
182 if (kmods = 255) then kmods := 0;
183 if (ev.kstate <> kmods) then exit;
184 if (mbuts <> 255) and (ev.bstate <> mbuts) then exit;
185 for f := 1 to High(e_KeyNames) do
186 begin
187 if (CompareText(kname, e_KeyNames[f]) = 0) then
188 begin
189 result := (ev.scan = f);
190 exit;
191 end;
192 end;
193 end;
196 operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
197 begin
198 result := (ev = s);
199 end;
202 operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
203 var
204 kmods: Byte = 255;
205 mbuts: Byte = 255;
206 kname: AnsiString;
207 but: Integer = -1;
208 begin
209 result := false;
211 if (Length(s) > 0) then
212 begin
213 if (s[1] = '+') then begin if (ev.kind <> ev.Press) then exit; end
214 else if (s[1] = '-') then begin if (ev.kind <> ev.Release) then exit; end
215 else if (s[1] = '*') then begin if (ev.kind <> ev.Motion) then exit; end
216 else if (ev.kind <> ev.Press) then exit;
217 end;
219 kname := parseModKeys(s, kmods, mbuts);
220 if (CompareText(kname, 'LMB') = 0) then but := THMouseEvent.Left
221 else if (CompareText(kname, 'RMB') = 0) then but := THMouseEvent.Right
222 else if (CompareText(kname, 'MMB') = 0) then but := THMouseEvent.Middle
223 else if (CompareText(kname, 'None') = 0) then but := 0
224 else exit;
226 //conwritefln('s=[%s]; kname=[%s]; kmods=%s; mbuts=%s; but=%s', [s, kname, kmods, mbuts, but]);
228 if (mbuts = 255) then mbuts := 0;
229 if (kmods = 255) then kmods := 0;
230 if (ev.kstate <> kmods) then exit;
232 if (ev.kind = ev.Press) then mbuts := mbuts or but
233 else if (ev.kind = ev.Release) then mbuts := mbuts and (not but);
235 //conwritefln(' ev.bstate=%s; ev.but=%s; mbuts=%s', [ev.bstate, ev.but, mbuts]);
237 result := (ev.bstate = mbuts) and (ev.but = but);
238 end;
241 operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
242 begin
243 result := (ev = s);
244 end;
247 // ////////////////////////////////////////////////////////////////////////// //
248 {$INCLUDE g_holmes_cmd.inc}
249 procedure holmesInitCommands (); forward;
250 procedure holmesInitBinds (); forward;
253 // ////////////////////////////////////////////////////////////////////////// //
254 var
255 g_ol_nice: Boolean = false;
256 g_ol_fill_walls: Boolean = false;
257 g_ol_rlayer_back: Boolean = false;
258 g_ol_rlayer_step: Boolean = false;
259 g_ol_rlayer_wall: Boolean = false;
260 g_ol_rlayer_door: Boolean = false;
261 g_ol_rlayer_acid1: Boolean = false;
262 g_ol_rlayer_acid2: Boolean = false;
263 g_ol_rlayer_water: Boolean = false;
264 g_ol_rlayer_fore: Boolean = false;
267 // ////////////////////////////////////////////////////////////////////////// //
268 var
269 winHelp: THTopWindow = nil;
270 winOptions: THTopWindow = nil;
271 winLayers: THTopWindow = nil;
272 winOutlines: THTopWindow = nil;
275 procedure createHelpWindow (); forward;
276 procedure createOptionsWindow (); forward;
277 procedure createLayersWindow (); forward;
278 procedure createOutlinesWindow (); forward;
281 procedure toggleLayersWindowCB (me: THControl; checked: Integer);
282 begin
283 if showLayersWindow then
284 begin
285 if (winLayers = nil) then createLayersWindow();
286 uiAddWindow(winLayers);
287 end
288 else
289 begin
290 uiRemoveWindow(winLayers);
291 end;
292 end;
295 procedure toggleOutlineWindowCB (me: THControl; checked: Integer);
296 begin
297 if showOutlineWindow then
298 begin
299 if (winOutlines = nil) then createOutlinesWindow();
300 uiAddWindow(winOutlines);
301 end
302 else
303 begin
304 uiRemoveWindow(winOutlines);
305 end;
306 end;
309 procedure createHelpWindow ();
310 var
311 llb: THCtlSimpleText;
312 slist: array of AnsiString = nil;
313 cmd: PHolmesCommand;
314 bind: THolmesBinding;
315 f, maxkeylen: Integer;
316 s: AnsiString;
317 begin
318 for cmd in cmdlist do cmd.helpmark := false;
320 maxkeylen := 0;
321 for bind in keybinds do
322 begin
323 if (Length(bind.key) = 0) then continue;
324 if cmdlist.get(bind.cmdName, cmd) then
325 begin
326 if (Length(cmd.help) > 0) then
327 begin
328 cmd.helpmark := true;
329 if (maxkeylen < Length(bind.key)) then maxkeylen := Length(bind.key);
330 end;
331 end;
332 end;
334 for cmd in cmdlist do
335 begin
336 if not cmd.helpmark then continue;
337 if (Length(cmd.help) = 0) then continue;
338 f := 0;
339 while (f < Length(slist)) and (CompareText(slist[f], cmd.section) <> 0) do Inc(f);
340 if (f = Length(slist)) then
341 begin
342 SetLength(slist, Length(slist)+1);
343 slist[High(slist)] := cmd.section;
344 end;
345 end;
347 llb := THCtlSimpleText.Create(0, 0);
348 for f := 0 to High(slist) do
349 begin
350 if (f > 0) then llb.appendItem('');
351 llb.appendItem(slist[f], true, true);
352 for cmd in cmdlist do
353 begin
354 if not cmd.helpmark then continue;
355 if (CompareText(cmd.section, slist[f]) <> 0) then continue;
356 for bind in keybinds do
357 begin
358 if (Length(bind.key) = 0) then continue;
359 if (cmd.name = bind.cmdName) then
360 begin
361 s := bind.key;
362 while (Length(s) < maxkeylen) do s += ' ';
363 s := ' '+s+' -- '+cmd.help;
364 llb.appendItem(s);
365 end;
366 end;
367 end;
368 end;
370 maxkeylen := 0;
371 for bind in msbinds do
372 begin
373 if (Length(bind.key) = 0) then continue;
374 if cmdlist.get(bind.cmdName, cmd) then
375 begin
376 if (Length(cmd.help) > 0) then
377 begin
378 cmd.helpmark := true;
379 if (maxkeylen < Length(bind.key)) then maxkeylen := Length(bind.key);
380 end;
381 end;
382 end;
384 llb.appendItem('');
385 llb.appendItem('mouse', true, true);
386 for bind in msbinds do
387 begin
388 if (Length(bind.key) = 0) then continue;
389 if cmdlist.get(bind.cmdName, cmd) then
390 begin
391 if (Length(cmd.help) > 0) then
392 begin
393 s := bind.key;
394 while (Length(s) < maxkeylen) do s += ' ';
395 s := ' '+s+' -- '+cmd.help;
396 llb.appendItem(s);
397 end;
398 end;
399 end;
401 winHelp := THTopWindow.Create('Holmes Help', 10, 10);
402 winHelp.escClose := true;
403 winHelp.appendChild(llb);
404 winHelp.centerInScreen();
405 end;
408 procedure winLayersClosed (me: THControl; dummy: Integer); begin showLayersWindow := false; end;
409 procedure winOutlinesClosed (me: THControl; dummy: Integer); begin showOutlineWindow := false; end;
411 procedure createLayersWindow ();
412 var
413 llb: THCtlCBListBox;
414 begin
415 llb := THCtlCBListBox.Create(0, 0);
416 llb.appendItem('background', @g_rlayer_back);
417 llb.appendItem('steps', @g_rlayer_step);
418 llb.appendItem('walls', @g_rlayer_wall);
419 llb.appendItem('doors', @g_rlayer_door);
420 llb.appendItem('acid1', @g_rlayer_acid1);
421 llb.appendItem('acid2', @g_rlayer_acid2);
422 llb.appendItem('water', @g_rlayer_water);
423 llb.appendItem('foreground', @g_rlayer_fore);
424 winLayers := THTopWindow.Create('layers', 10, 10);
425 winLayers.escClose := true;
426 winLayers.appendChild(llb);
427 winLayers.closeCB := winLayersClosed;
428 end;
431 procedure createOutlinesWindow ();
432 var
433 llb: THCtlCBListBox;
434 begin
435 llb := THCtlCBListBox.Create(0, 0);
436 llb.appendItem('background', @g_ol_rlayer_back);
437 llb.appendItem('steps', @g_ol_rlayer_step);
438 llb.appendItem('walls', @g_ol_rlayer_wall);
439 llb.appendItem('doors', @g_ol_rlayer_door);
440 llb.appendItem('acid1', @g_ol_rlayer_acid1);
441 llb.appendItem('acid2', @g_ol_rlayer_acid2);
442 llb.appendItem('water', @g_ol_rlayer_water);
443 llb.appendItem('foreground', @g_ol_rlayer_fore);
444 llb.appendItem('OPTIONS', nil);
445 llb.appendItem('fill walls', @g_ol_fill_walls);
446 llb.appendItem('contours', @g_ol_nice);
447 winOutlines := THTopWindow.Create('outlines', 100, 10);
448 winOutlines.escClose := true;
449 winOutlines.appendChild(llb);
450 winOutlines.closeCB := winOutlinesClosed;
451 end;
454 procedure createOptionsWindow ();
455 var
456 llb: THCtlCBListBox;
457 begin
458 llb := THCtlCBListBox.Create(0, 0);
459 llb.appendItem('map grid', @showGrid);
460 llb.appendItem('cursor position on map', @showMapCurPos);
461 llb.appendItem('monster info', @showMonsInfo);
462 llb.appendItem('monster LOS to player', @showMonsLOS2Plr);
463 llb.appendItem('monster cells (SLOW!)', @showAllMonsCells);
464 llb.appendItem('WINDOWS', nil);
465 llb.appendItem('layers window', @showLayersWindow, toggleLayersWindowCB);
466 llb.appendItem('outline window', @showOutlineWindow, toggleOutlineWindowCB);
467 winOptions := THTopWindow.Create('Holmes Options', 100, 100);
468 winOptions.escClose := true;
469 winOptions.appendChild(llb);
470 winOptions.centerInScreen();
471 end;
474 procedure toggleLayersWindow (arg: Integer=-1);
475 begin
476 showLayersWindow := not showLayersWindow;
477 toggleLayersWindowCB(nil, 0);
478 end;
480 procedure toggleOutlineWindow (arg: Integer=-1);
481 begin
482 showOutlineWindow := not showOutlineWindow;
483 toggleOutlineWindowCB(nil, 0);
484 end;
486 procedure toggleHelpWindow (arg: Integer=-1);
487 begin
488 if (winHelp = nil) then createHelpWindow();
489 if not uiVisibleWindow(winHelp) then uiAddWindow(winHelp) else uiRemoveWindow(winHelp);
490 end;
492 procedure toggleOptionsWindow (arg: Integer=-1);
493 begin
494 if (winOptions = nil) then createOptionsWindow();
495 if not uiVisibleWindow(winOptions) then uiAddWindow(winOptions) else uiRemoveWindow(winOptions);
496 end;
499 // ////////////////////////////////////////////////////////////////////////// //
500 procedure g_Holmes_VidModeChanged ();
501 begin
502 e_WriteLog(Format('Holmes: videomode changed: %dx%d', [gScreenWidth, gScreenHeight]), MSG_NOTIFY);
503 // texture space is possibly lost here, idc
504 curtexid := 0;
505 font6texid := 0;
506 font8texid := 0;
507 prfont6texid := 0;
508 prfont8texid := 0;
509 //createCursorTexture();
510 end;
512 procedure g_Holmes_WindowFocused ();
513 begin
514 msB := 0;
515 kbS := 0;
516 end;
518 procedure g_Holmes_WindowBlured ();
519 begin
520 end;
523 // ////////////////////////////////////////////////////////////////////////// //
524 var
525 vpSet: Boolean = false;
526 vpx, vpy: Integer;
527 vpw, vph: Integer;
528 laserSet: Boolean = false;
529 laserX0, laserY0, laserX1, laserY1: Integer;
530 monMarkedUID: Integer = -1;
533 procedure g_Holmes_plrView (viewPortX, viewPortY, viewPortW, viewPortH: Integer);
534 begin
535 vpSet := true;
536 vpx := viewPortX;
537 vpy := viewPortY;
538 vpw := viewPortW;
539 vph := viewPortH;
540 end;
542 procedure g_Holmes_plrLaser (ax0, ay0, ax1, ay1: Integer);
543 begin
544 laserSet := true;
545 laserX0 := ax0;
546 laserY0 := ay0;
547 laserX1 := ax1;
548 laserY1 := ay1;
549 laserSet := laserSet; // shut up, fpc!
550 end;
553 function pmsCurMapX (): Integer; inline; begin result := msX+vpx; end;
554 function pmsCurMapY (): Integer; inline; begin result := msY+vpy; end;
557 procedure plrDebugMouse (var ev: THMouseEvent);
558 begin
559 //e_WriteLog(Format('mouse: x=%d; y=%d; but=%d; bstate=%d', [msx, msy, but, bstate]), MSG_NOTIFY);
560 if (gPlayer1 = nil) or not vpSet then exit;
561 //if (ev.kind <> THMouseEvent.Press) then exit;
562 //e_WriteLog(Format('mev: %d', [Integer(ev.kind)]), MSG_NOTIFY);
563 msbindExecute(ev);
564 end;
567 var
568 edgeBmp: array of Byte = nil;
571 procedure drawOutlines ();
572 var
573 r, g, b: Integer;
575 procedure clearEdgeBmp ();
576 begin
577 SetLength(edgeBmp, (gWinSizeX+4)*(gWinSizeY+4));
578 FillChar(edgeBmp[0], Length(edgeBmp)*sizeof(edgeBmp[0]), 0);
579 end;
581 procedure drawPanel (pan: TPanel);
582 var
583 sx, len, y0, y1: Integer;
584 begin
585 if (pan = nil) or (pan.Width < 1) or (pan.Height < 1) then exit;
586 if (pan.X+pan.Width <= vpx-1) or (pan.Y+pan.Height <= vpy-1) then exit;
587 if (pan.X >= vpx+vpw+1) or (pan.Y >= vpy+vph+1) then exit;
588 if g_ol_nice or g_ol_fill_walls then
589 begin
590 sx := pan.X-(vpx-1);
591 len := pan.Width;
592 if (len > gWinSizeX+4) then len := gWinSizeX+4;
593 if (sx < 0) then begin len += sx; sx := 0; end;
594 if (sx+len > gWinSizeX+4) then len := gWinSizeX+4-sx;
595 if (len < 1) then exit;
596 assert(sx >= 0);
597 assert(sx+len <= gWinSizeX+4);
598 y0 := pan.Y-(vpy-1);
599 y1 := y0+pan.Height;
600 if (y0 < 0) then y0 := 0;
601 if (y1 > gWinSizeY+4) then y1 := gWinSizeY+4;
602 while (y0 < y1) do
603 begin
604 FillChar(edgeBmp[y0*(gWinSizeX+4)+sx], len*sizeof(edgeBmp[0]), 1);
605 Inc(y0);
606 end;
607 end
608 else
609 begin
610 drawRect(pan.X, pan.Y, pan.Width, pan.Height, r, g, b);
611 end;
612 end;
614 var
615 lsx: Integer = -1;
616 lex: Integer = -1;
617 lsy: Integer = -1;
619 procedure flushLine ();
620 begin
621 if (lsy > 0) and (lsx > 0) then
622 begin
623 if (lex = lsx) then
624 begin
625 glBegin(GL_POINTS);
626 glVertex2f(lsx-1+vpx+0.37, lsy-1+vpy+0.37);
627 glEnd();
628 end
629 else
630 begin
631 glBegin(GL_LINES);
632 glVertex2f(lsx-1+vpx+0.37, lsy-1+vpy+0.37);
633 glVertex2f(lex-0+vpx+0.37, lsy-1+vpy+0.37);
634 glEnd();
635 end;
636 end;
637 lsx := -1;
638 lex := -1;
639 end;
641 procedure startLine (y: Integer);
642 begin
643 flushLine();
644 lsy := y;
645 end;
647 procedure putPixel (x: Integer);
648 begin
649 if (x < 1) then exit;
650 if (lex+1 <> x) then flushLine();
651 if (lsx < 0) then lsx := x;
652 lex := x;
653 end;
655 procedure drawEdges ();
656 var
657 x, y: Integer;
658 a: PByte;
659 begin
660 glDisable(GL_BLEND);
661 glDisable(GL_TEXTURE_2D);
662 glLineWidth(1);
663 glPointSize(1);
664 glDisable(GL_LINE_SMOOTH);
665 glDisable(GL_POLYGON_SMOOTH);
666 glColor4f(r/255.0, g/255.0, b/255.0, 1.0);
667 for y := 1 to vph do
668 begin
669 a := @edgeBmp[y*(gWinSizeX+4)+1];
670 startLine(y);
671 for x := 1 to vpw do
672 begin
673 if (a[0] <> 0) then
674 begin
675 if (a[-1] = 0) or (a[1] = 0) or (a[-(gWinSizeX+4)] = 0) or (a[gWinSizeX+4] = 0) or
676 (a[-(gWinSizeX+4)-1] = 0) or (a[-(gWinSizeX+4)+1] = 0) or
677 (a[gWinSizeX+4-1] = 0) or (a[gWinSizeX+4+1] = 0) then
678 begin
679 putPixel(x);
680 end;
681 end;
682 Inc(a);
683 end;
684 flushLine();
685 end;
686 end;
688 procedure drawFilledWalls ();
689 var
690 x, y: Integer;
691 a: PByte;
692 begin
693 glDisable(GL_BLEND);
694 glDisable(GL_TEXTURE_2D);
695 glLineWidth(1);
696 glPointSize(1);
697 glDisable(GL_LINE_SMOOTH);
698 glDisable(GL_POLYGON_SMOOTH);
699 glColor4f(r/255.0, g/255.0, b/255.0, 1.0);
700 for y := 1 to vph do
701 begin
702 a := @edgeBmp[y*(gWinSizeX+4)+1];
703 startLine(y);
704 for x := 1 to vpw do
705 begin
706 if (a[0] <> 0) then putPixel(x);
707 Inc(a);
708 end;
709 flushLine();
710 end;
711 end;
713 procedure doWallsOld (parr: array of TPanel; ptype: Word; ar, ag, ab: Integer);
714 var
715 f: Integer;
716 pan: TPanel;
717 begin
718 r := ar;
719 g := ag;
720 b := ab;
721 if g_ol_nice or g_ol_fill_walls then clearEdgeBmp();
722 for f := 0 to High(parr) do
723 begin
724 pan := parr[f];
725 if (pan = nil) or not pan.Enabled or (pan.Width < 1) or (pan.Height < 1) then continue;
726 if ((pan.PanelType and ptype) = 0) then continue;
727 drawPanel(pan);
728 end;
729 if g_ol_nice then drawEdges();
730 if g_ol_fill_walls then drawFilledWalls();
731 end;
733 var
734 xptag: Word;
736 function doWallCB (pan: TPanel; tag: Integer): Boolean;
737 begin
738 result := false; // don't stop
739 //if (pan = nil) or not pan.Enabled or (pan.Width < 1) or (pan.Height < 1) then exit;
740 if ((pan.PanelType and xptag) = 0) then exit;
741 drawPanel(pan);
742 end;
744 procedure doWalls (parr: array of TPanel; ptype: Word; ar, ag, ab: Integer);
745 begin
746 r := ar;
747 g := ag;
748 b := ab;
749 xptag := ptype;
750 if ((ptype and (PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR)) <> 0) then ptype := GridTagWall or GridTagDoor
751 else panelTypeToTag(ptype);
752 if g_ol_nice or g_ol_fill_walls then clearEdgeBmp();
753 mapGrid.forEachInAABB(vpx-1, vpy-1, vpw+2, vph+2, doWallCB, ptype);
754 if g_ol_nice then drawEdges();
755 if g_ol_fill_walls then drawFilledWalls();
756 end;
758 begin
759 if g_ol_rlayer_back then doWallsOld(gRenderBackgrounds, PANEL_BACK, 255, 127, 0);
760 if g_ol_rlayer_step then doWallsOld(gSteps, PANEL_STEP, 192, 192, 192);
761 if g_ol_rlayer_wall then doWallsOld(gWalls, PANEL_WALL, 255, 255, 255);
762 if g_ol_rlayer_door then doWallsOld(gWalls, PANEL_OPENDOOR or PANEL_CLOSEDOOR, 0, 255, 0);
763 if g_ol_rlayer_acid1 then doWallsOld(gAcid1, PANEL_ACID1, 255, 0, 0);
764 if g_ol_rlayer_acid2 then doWallsOld(gAcid2, PANEL_ACID2, 198, 198, 0);
765 if g_ol_rlayer_water then doWallsOld(gWater, PANEL_WATER, 0, 255, 255);
766 if g_ol_rlayer_fore then doWallsOld(gRenderForegrounds, PANEL_FORE, 210, 210, 210);
767 end;
770 procedure plrDebugDraw ();
771 procedure drawTileGrid ();
772 var
773 x, y: Integer;
774 begin
775 for y := 0 to (mapGrid.gridHeight div mapGrid.tileSize) do
776 begin
777 drawLine(mapGrid.gridX0, mapGrid.gridY0+y*mapGrid.tileSize, mapGrid.gridX0+mapGrid.gridWidth, mapGrid.gridY0+y*mapGrid.tileSize, 96, 96, 96, 255);
778 end;
780 for x := 0 to (mapGrid.gridWidth div mapGrid.tileSize) do
781 begin
782 drawLine(mapGrid.gridX0+x*mapGrid.tileSize, mapGrid.gridY0, mapGrid.gridX0+x*mapGrid.tileSize, mapGrid.gridY0+y*mapGrid.gridHeight, 96, 96, 96, 255);
783 end;
784 end;
786 procedure hilightCell (cx, cy: Integer);
787 begin
788 fillRect(cx, cy, monsGrid.tileSize, monsGrid.tileSize, 0, 128, 0, 64);
789 end;
791 procedure hilightCell1 (cx, cy: Integer);
792 begin
793 //e_WriteLog(Format('h1: (%d,%d)', [cx, cy]), MSG_NOTIFY);
794 fillRect(cx, cy, monsGrid.tileSize, monsGrid.tileSize, 255, 255, 0, 92);
795 end;
797 function hilightWallTrc (pan: TPanel; tag: Integer; x, y, prevx, prevy: Integer): Boolean;
798 begin
799 result := false; // don't stop
800 if (pan = nil) then exit; // cell completion, ignore
801 //e_WriteLog(Format('h1: (%d,%d)', [cx, cy]), MSG_NOTIFY);
802 fillRect(pan.X, pan.Y, pan.Width, pan.Height, 0, 128, 128, 64);
803 end;
805 function monsCollector (mon: TMonster; tag: Integer): Boolean;
806 var
807 ex, ey: Integer;
808 mx, my, mw, mh: Integer;
809 begin
810 result := false;
811 mon.getMapBox(mx, my, mw, mh);
812 e_DrawQuad(mx, my, mx+mw-1, my+mh-1, 255, 255, 0, 96);
813 if lineAABBIntersects(laserX0, laserY0, laserX1, laserY1, mx, my, mw, mh, ex, ey) then
814 begin
815 e_DrawPoint(8, ex, ey, 0, 255, 0);
816 end;
817 end;
819 procedure drawMonsterInfo (mon: TMonster);
820 var
821 mx, my, mw, mh: Integer;
823 procedure drawMonsterTargetLine ();
824 var
825 emx, emy, emw, emh: Integer;
826 enemy: TMonster;
827 eplr: TPlayer;
828 ex, ey: Integer;
829 begin
830 if (g_GetUIDType(mon.MonsterTargetUID) = UID_PLAYER) then
831 begin
832 eplr := g_Player_Get(mon.MonsterTargetUID);
833 if (eplr <> nil) then eplr.getMapBox(emx, emy, emw, emh) else exit;
834 end
835 else if (g_GetUIDType(mon.MonsterTargetUID) = UID_MONSTER) then
836 begin
837 enemy := g_Monsters_ByUID(mon.MonsterTargetUID);
838 if (enemy <> nil) then enemy.getMapBox(emx, emy, emw, emh) else exit;
839 end
840 else
841 begin
842 exit;
843 end;
844 mon.getMapBox(mx, my, mw, mh);
845 drawLine(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, 255, 0, 0, 255);
846 if (g_Map_traceToNearestWall(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, @ex, @ey) <> nil) then
847 begin
848 drawLine(mx+mw div 2, my+mh div 2, ex, ey, 0, 255, 0, 255);
849 end;
850 end;
852 procedure drawLOS2Plr ();
853 var
854 emx, emy, emw, emh: Integer;
855 eplr: TPlayer;
856 ex, ey: Integer;
857 begin
858 eplr := gPlayers[0];
859 if (eplr = nil) then exit;
860 eplr.getMapBox(emx, emy, emw, emh);
861 mon.getMapBox(mx, my, mw, mh);
862 drawLine(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, 255, 0, 0, 255);
863 {$IF DEFINED(D2F_DEBUG)}
864 //mapGrid.dbgRayTraceTileHitCB := hilightCell1;
865 {$ENDIF}
866 if (g_Map_traceToNearestWall(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, @ex, @ey) <> nil) then
867 //if (mapGrid.traceRay(ex, ey, mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, hilightWallTrc, (GridTagWall or GridTagDoor)) <> nil) then
868 begin
869 drawLine(mx+mw div 2, my+mh div 2, ex, ey, 0, 255, 0, 255);
870 end;
871 {$IF DEFINED(D2F_DEBUG)}
872 //mapGrid.dbgRayTraceTileHitCB := nil;
873 {$ENDIF}
874 end;
876 begin
877 if (mon = nil) then exit;
878 mon.getMapBox(mx, my, mw, mh);
879 //mx += mw div 2;
881 monsGrid.forEachBodyCell(mon.proxyId, hilightCell);
883 if showMonsInfo then
884 begin
885 //fillRect(mx-4, my-7*8-6, 110, 7*8+6, 0, 0, 94, 250);
886 darkenRect(mx-4, my-7*8-6, 110, 7*8+6, 128);
887 my -= 8;
888 my -= 2;
890 // type
891 drawText6(mx, my, Format('%s(U:%u)', [monsTypeToString(mon.MonsterType), mon.UID]), 255, 127, 0); my -= 8;
892 // beh
893 drawText6(mx, my, Format('Beh: %s', [monsBehToString(mon.MonsterBehaviour)]), 255, 127, 0); my -= 8;
894 // state
895 drawText6(mx, my, Format('State:%s (%d)', [monsStateToString(mon.MonsterState), mon.MonsterSleep]), 255, 127, 0); my -= 8;
896 // health
897 drawText6(mx, my, Format('Health:%d', [mon.MonsterHealth]), 255, 127, 0); my -= 8;
898 // ammo
899 drawText6(mx, my, Format('Ammo:%d', [mon.MonsterAmmo]), 255, 127, 0); my -= 8;
900 // target
901 drawText6(mx, my, Format('TgtUID:%u', [mon.MonsterTargetUID]), 255, 127, 0); my -= 8;
902 drawText6(mx, my, Format('TgtTime:%d', [mon.MonsterTargetTime]), 255, 127, 0); my -= 8;
903 end;
905 drawMonsterTargetLine();
906 if showMonsLOS2Plr then drawLOS2Plr();
908 property MonsterRemoved: Boolean read FRemoved write FRemoved;
909 property MonsterPain: Integer read FPain write FPain;
910 property MonsterAnim: Byte read FCurAnim write FCurAnim;
912 end;
914 function highlightAllMonsterCells (mon: TMonster): Boolean;
915 begin
916 result := false; // don't stop
917 monsGrid.forEachBodyCell(mon.proxyId, hilightCell);
918 end;
920 var
921 mon: TMonster;
922 mx, my, mw, mh: Integer;
923 begin
924 if (gPlayer1 = nil) then exit;
926 glEnable(GL_SCISSOR_TEST);
927 glScissor(0, gWinSizeY-gPlayerScreenSize.Y-1, vpw, vph);
929 glPushMatrix();
930 glTranslatef(-vpx, -vpy, 0);
932 if (showGrid) then drawTileGrid();
933 drawOutlines();
935 if (laserSet) then g_Mons_AlongLine(laserX0, laserY0, laserX1, laserY1, monsCollector, true);
937 if (monMarkedUID <> -1) then
938 begin
939 mon := g_Monsters_ByUID(monMarkedUID);
940 if (mon <> nil) then
941 begin
942 mon.getMapBox(mx, my, mw, mh);
943 e_DrawQuad(mx, my, mx+mw-1, my+mh-1, 255, 0, 0, 30);
944 drawMonsterInfo(mon);
945 end;
946 end;
948 if showAllMonsCells then g_Mons_ForEach(highlightAllMonsterCells);
950 glPopMatrix();
952 glDisable(GL_SCISSOR_TEST);
954 if showMapCurPos then drawText8(4, gWinSizeY-10, Format('mappos:(%d,%d)', [pmsCurMapX, pmsCurMapY]), 255, 255, 0);
955 end;
958 // ////////////////////////////////////////////////////////////////////////// //
959 function g_Holmes_MouseEvent (var ev: THMouseEvent): Boolean;
960 begin
961 holmesInitCommands();
962 holmesInitBinds();
963 result := true;
964 msX := ev.x;
965 msY := ev.y;
966 msB := ev.bstate;
967 kbS := ev.kstate;
968 msB := msB;
969 if not uiMouseEvent(ev) then plrDebugMouse(ev);
970 end;
973 // ////////////////////////////////////////////////////////////////////////// //
974 function g_Holmes_KeyEvent (var ev: THKeyEvent): Boolean;
975 {$IF DEFINED(D2F_DEBUG)}
976 var
977 pan: TPanel;
978 ex, ey: Integer;
979 dx, dy: Integer;
980 {$ENDIF}
982 procedure dummyWallTrc (cx, cy: Integer);
983 begin
984 end;
986 begin
987 holmesInitCommands();
988 holmesInitBinds();
989 result := false;
990 msB := ev.bstate;
991 kbS := ev.kstate;
992 case ev.scan of
993 SDL_SCANCODE_LCTRL, SDL_SCANCODE_RCTRL,
994 SDL_SCANCODE_LALT, SDL_SCANCODE_RALT,
995 SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT:
996 result := true;
997 end;
998 if uiKeyEvent(ev) then begin result := true; exit; end;
999 if keybindExecute(ev) then begin result := true; exit; end;
1000 // press
1001 if (ev.kind = THKeyEvent.Press) then
1002 begin
1003 {$IF DEFINED(D2F_DEBUG)}
1004 // C-UP, C-DOWN, C-LEFT, C-RIGHT: trace 10 pixels from cursor in the respective direction
1005 if ((ev.scan = SDL_SCANCODE_UP) or (ev.scan = SDL_SCANCODE_DOWN) or (ev.scan = SDL_SCANCODE_LEFT) or (ev.scan = SDL_SCANCODE_RIGHT)) and
1006 ((ev.kstate and THKeyEvent.ModCtrl) <> 0) then
1007 begin
1008 result := true;
1009 dx := pmsCurMapX;
1010 dy := pmsCurMapY;
1011 case ev.scan of
1012 SDL_SCANCODE_UP: dy -= 120;
1013 SDL_SCANCODE_DOWN: dy += 120;
1014 SDL_SCANCODE_LEFT: dx -= 120;
1015 SDL_SCANCODE_RIGHT: dx += 120;
1016 end;
1017 {$IF DEFINED(D2F_DEBUG)}
1018 //mapGrid.dbgRayTraceTileHitCB := dummyWallTrc;
1019 mapGrid.dbgShowTraceLog := true;
1020 {$ENDIF}
1021 pan := g_Map_traceToNearest(pmsCurMapX, pmsCurMapY, dx, dy, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1022 {$IF DEFINED(D2F_DEBUG)}
1023 //mapGrid.dbgRayTraceTileHitCB := nil;
1024 mapGrid.dbgShowTraceLog := false;
1025 {$ENDIF}
1026 e_LogWritefln('v-trace: (%d,%d)-(%d,%d); end=(%d,%d); hit=%d', [pmsCurMapX, pmsCurMapY, dx, dy, ex, ey, (pan <> nil)]);
1027 exit;
1028 end;
1029 {$ENDIF}
1030 end;
1031 end;
1034 // ////////////////////////////////////////////////////////////////////////// //
1035 procedure g_Holmes_Draw ();
1036 begin
1037 holmesInitCommands();
1038 holmesInitBinds();
1040 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
1041 glDisable(GL_STENCIL_TEST);
1042 glDisable(GL_BLEND);
1043 glDisable(GL_SCISSOR_TEST);
1044 glDisable(GL_TEXTURE_2D);
1046 if gGameOn then
1047 begin
1048 plrDebugDraw();
1049 end;
1051 laserSet := false;
1052 end;
1055 procedure g_Holmes_DrawUI ();
1056 begin
1057 uiDraw();
1058 drawCursor();
1059 end;
1062 // ////////////////////////////////////////////////////////////////////////// //
1063 procedure bcOneMonsterThinkStep (); begin gmon_debug_think := false; gmon_debug_one_think_step := true; end;
1064 procedure bcToggleMonsterInfo (arg: Integer=-1); begin if (arg < 0) then showMonsInfo := not showMonsInfo else showMonsInfo := (arg > 0); end;
1065 procedure bcToggleMonsterLOSPlr (arg: Integer=-1); begin if (arg < 0) then showMonsLOS2Plr := not showMonsLOS2Plr else showMonsLOS2Plr := (arg > 0); end;
1066 procedure bcToggleMonsterCells (arg: Integer=-1); begin if (arg < 0) then showAllMonsCells := not showAllMonsCells else showAllMonsCells := (arg > 0); end;
1068 procedure bcToggleCurPos (arg: Integer=-1); begin if (arg < 0) then showMapCurPos := not showMapCurPos else showMapCurPos := (arg > 0); end;
1069 procedure bcToggleGrid (arg: Integer=-1); begin if (arg < 0) then showGrid := not showGrid else showGrid := (arg > 0); end;
1071 procedure bcMonsterWakeup ();
1072 var
1073 mon: TMonster;
1074 begin
1075 if (monMarkedUID <> -1) then
1076 begin
1077 mon := g_Monsters_ByUID(monMarkedUID);
1078 if (mon <> nil) then mon.WakeUp();
1079 end;
1080 end;
1082 procedure bcPlayerTeleport ();
1083 var
1084 x, y, w, h: Integer;
1085 begin
1086 //e_WriteLog(Format('TELEPORT: (%d,%d)', [pmsCurMapX, pmsCurMapY]), MSG_NOTIFY);
1087 if (gPlayers[0] <> nil) then
1088 begin
1089 gPlayers[0].getMapBox(x, y, w, h);
1090 gPlayers[0].TeleportTo(pmsCurMapX-w div 2, pmsCurMapY-h div 2, true, 69); // 69: don't change dir
1091 end;
1092 end;
1094 procedure cbAtcurSelectMonster ();
1095 function monsAtDump (mon: TMonster; tag: Integer): Boolean;
1096 begin
1097 result := true; // stop
1098 e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), MSG_NOTIFY);
1099 monMarkedUID := mon.UID;
1100 end;
1101 begin
1102 monMarkedUID := -1;
1103 //e_WriteLog('===========================', MSG_NOTIFY);
1104 monsGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, monsAtDump);
1105 //e_WriteLog('---------------------------', MSG_NOTIFY);
1106 end;
1108 procedure cbAtcurDumpMonsters ();
1109 function monsAtDump (mon: TMonster; tag: Integer): Boolean;
1110 begin
1111 result := false; // don't stop
1112 e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), MSG_NOTIFY);
1113 end;
1114 begin
1115 e_WriteLog('===========================', MSG_NOTIFY);
1116 monsGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, monsAtDump);
1117 e_WriteLog('---------------------------', MSG_NOTIFY);
1118 end;
1120 procedure cbAtcurDumpWalls ();
1121 function wallToggle (pan: TPanel; tag: Integer): Boolean;
1122 begin
1123 result := false; // don't stop
1124 e_WriteLog(Format('wall #%d(%d); enabled=%d (%d); (%d,%d)-(%d,%d)', [pan.arrIdx, pan.proxyId, Integer(pan.Enabled), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.X, pan.Y, pan.Width, pan.Height]), MSG_NOTIFY);
1125 end;
1126 begin
1127 e_WriteLog('=== TOGGLE WALL ===', MSG_NOTIFY);
1128 mapGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, wallToggle, (GridTagWall or GridTagDoor));
1129 e_WriteLog('--- toggle wall ---', MSG_NOTIFY);
1130 end;
1132 procedure cbAtcurToggleWalls ();
1133 function wallToggle (pan: TPanel; tag: Integer): Boolean;
1134 begin
1135 result := false; // don't stop
1136 //e_WriteLog(Format('wall #%d(%d); enabled=%d (%d); (%d,%d)-(%d,%d)', [pan.arrIdx, pan.proxyId, Integer(pan.Enabled), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.X, pan.Y, pan.Width, pan.Height]), MSG_NOTIFY);
1137 if pan.Enabled then g_Map_DisableWall(pan.arrIdx) else g_Map_EnableWall(pan.arrIdx);
1138 end;
1139 begin
1140 //e_WriteLog('=== TOGGLE WALL ===', MSG_NOTIFY);
1141 mapGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, wallToggle, (GridTagWall or GridTagDoor));
1142 //e_WriteLog('--- toggle wall ---', MSG_NOTIFY);
1143 end;
1146 // ////////////////////////////////////////////////////////////////////////// //
1147 procedure holmesInitCommands ();
1148 begin
1149 if (cmdlist <> nil) then exit;
1150 cmdAdd('win_layers', toggleLayersWindow, 'toggle layers window', 'window control');
1151 cmdAdd('win_outline', toggleOutlineWindow, 'toggle outline window', 'window control');
1152 cmdAdd('win_help', toggleHelpWindow, 'toggle help window', 'window control');
1153 cmdAdd('win_options', toggleOptionsWindow, 'toggle options window', 'window control');
1155 cmdAdd('mon_think_step', bcOneMonsterThinkStep, 'one monster think step', 'monster control');
1156 cmdAdd('mon_info', bcToggleMonsterInfo, 'toggle monster info', 'monster control');
1157 cmdAdd('mon_los_plr', bcToggleMonsterLOSPlr, 'toggle monster LOS to player', 'monster control');
1158 cmdAdd('mon_cells', bcToggleMonsterCells, 'toggle "show all cells occupied by monsters" (SLOW!)', 'monster control');
1159 cmdAdd('mon_wakeup', bcMonsterWakeup, 'toggle "show all cells occupied by monsters" (SLOW!)', 'monster control');
1161 cmdAdd('plr_teleport', bcPlayerTeleport, 'teleport player', 'player control');
1163 cmdAdd('dbg_curpos', bcToggleCurPos, 'toggle "show cursor position on the map"', 'various');
1164 cmdAdd('dbg_grid', bcToggleGrid, 'toggle grid', 'various');
1166 cmdAdd('atcur_select_monster', cbAtcurSelectMonster, 'select monster to operate', 'monster control');
1167 cmdAdd('atcur_dump_monsters', cbAtcurDumpMonsters, 'dump monsters in cell', 'monster control');
1168 cmdAdd('atcur_dump_walls', cbAtcurDumpWalls, 'dump walls in cell', 'wall control');
1169 cmdAdd('atcur_disable_walls', cbAtcurToggleWalls, 'disable walls', 'wall control');
1170 end;
1173 procedure holmesInitBinds ();
1174 var
1175 st: TStream = nil;
1176 pr: TTextParser = nil;
1177 s, kn, v: AnsiString;
1178 kmods: Byte;
1179 mbuts: Byte;
1180 begin
1181 kbS := kbS;
1182 if not keybindsInited then
1183 begin
1184 // keyboard
1185 keybindAdd('F1', 'win_help');
1186 keybindAdd('M-F1', 'win_options');
1187 keybindAdd('C-O', 'win_outline');
1188 keybindAdd('C-L', 'win_layers');
1190 keybindAdd('M-M', 'mon_think_step');
1191 keybindAdd('M-I', 'mon_info');
1192 keybindAdd('M-L', 'mon_los_plr');
1193 keybindAdd('M-G', 'mon_cells');
1194 keybindAdd('M-A', 'mon_wakeup');
1196 keybindAdd('C-T', 'plr_teleport');
1198 keybindAdd('C-P', 'dbg_curpos');
1199 keybindAdd('C-G', 'dbg_grid');
1201 // mouse
1202 msbindAdd('LMB', 'atcur_select_monster');
1203 msbindAdd('M-LMB', 'atcur_dump_monsters');
1204 msbindAdd('RMB', 'atcur_dump_walls');
1205 msbindAdd('M-RMB', 'atcur_disable_walls');
1207 // load bindings from file
1208 try
1209 st := openDiskFileRO(GameDir+'holmes.rc');
1210 pr := TFileTextParser.Create(st);
1211 conwriteln('parsing "holmes.rc"...');
1212 while (pr.tokType <> pr.TTEOF) do
1213 begin
1214 s := pr.expectId();
1215 if (s = 'stop') then break
1216 else if (s = 'unbind_keys') then keybinds := nil
1217 else if (s = 'unbind_mouse') then msbinds := nil
1218 else if (s = 'bind') then
1219 begin
1220 if (pr.tokType = pr.TTStr) then s := pr.expectStr(false)
1221 else if (pr.tokType = pr.TTInt) then s := Format('%d', [pr.expectInt()])
1222 else s := pr.expectId();
1224 if (pr.tokType = pr.TTStr) then v := pr.expectStr(false)
1225 else if (pr.tokType = pr.TTInt) then v := Format('%d', [pr.expectInt()])
1226 else v := pr.expectId();
1228 kn := parseModKeys(s, kmods, mbuts);
1229 if (CompareText(kn, 'lmb') = 0) or (CompareText(kn, 'rmb') = 0) or (CompareText(kn, 'mmb') = 0) or (CompareText(kn, 'None') = 0) then
1230 begin
1231 msbindAdd(s, v);
1232 end
1233 else
1234 begin
1235 keybindAdd(s, v);
1236 end;
1237 end;
1238 end;
1239 except on e: Exception do // sorry
1240 if (pr <> nil) then conwritefln('Holmes config parse error at (%s,%s): %s', [pr.tokLine, pr.tokCol, e.message]);
1241 end;
1242 if (pr <> nil) then pr.Free() else st.Free(); // ownership
1243 end;
1244 end;
1247 end.