DEADSOFTWARE

mempool is optional now
[d2df-sdl.git] / src / game / g_holmes.pas
index 9f98ca3172d7c9588605a5d5115aebe4db45f114..94e61712c56dbd741a4cdb13a7626dbf2054cfcc 100644 (file)
@@ -19,62 +19,13 @@ unit g_holmes;
 interface
 
 uses
+  {$IFDEF USE_MEMPOOL}mempool,{$ENDIF} geom,
   e_log, e_input,
   g_textures, g_basic, e_graphics, g_phys, g_grid, g_player, g_monsters,
-  g_window, g_map, g_triggers, g_items, g_game, g_panel, g_console,
-  xprofiler;
-
-
-type
-  THMouseEvent = record
-  public
-    const
-      // both for but and for bstate
-      Left = $0001;
-      Right = $0002;
-      Middle = $0004;
-      WheelUp = $0008;
-      WheelDown = $0010;
-
-      // event types
-      Release = 0;
-      Press = 1;
-      Motion = 2;
-
-  public
-    kind: Byte; // motion, press, release
-    x, y: Integer;
-    dx, dy: Integer; // for wheel this is wheel motion, otherwise this is relative mouse motion
-    but: Word; // current pressed/released button, or 0 for motion
-    bstate: Word; // button state
-    kstate: Word; // keyboard state (see THKeyEvent);
-  end;
+  g_window, g_map, g_triggers, g_items, g_game, g_panel, g_console, g_gfx,
+  xprofiler,
+  sdlcarcass, glgfx, gh_ui;
 
-  THKeyEvent = record
-  public
-    const
-      // modifiers
-      ModCtrl = $0001;
-      ModAlt = $0002;
-      ModShift = $0004;
-
-      // event types
-      Release = 0;
-      Press = 1;
-
-  public
-    kind: Byte;
-    scan: Word; // SDL_SCANCODE_XXX
-    sym: Word; // SDLK_XXX
-    bstate: Word; // button state
-    kstate: Word; // keyboard state
-
-  public
-  end;
-
-procedure g_Holmes_VidModeChanged ();
-procedure g_Holmes_WindowFocused ();
-procedure g_Holmes_WindowBlured ();
 
 procedure g_Holmes_Draw ();
 procedure g_Holmes_DrawUI ();
@@ -83,26 +34,19 @@ function g_Holmes_MouseEvent (var ev: THMouseEvent): Boolean; // returns `true`
 function g_Holmes_KeyEvent (var ev: THKeyEvent): Boolean; // returns `true` if event was eaten
 
 // hooks for player
-procedure g_Holmes_plrView (viewPortX, viewPortY, viewPortW, viewPortH: Integer);
+procedure g_Holmes_plrViewPos (viewPortX, viewPortY: Integer);
+procedure g_Holmes_plrViewSize (viewPortW, viewPortH: Integer);
 procedure g_Holmes_plrLaser (ax0, ay0, ax1, ay1: Integer);
 
 
-operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
-
-operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
-
-
 var
   g_holmes_enabled: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}false{$ENDIF};
-  g_holmes_ui_scale: Single = 1.0;
 
 
 implementation
 
 uses
-  {rttiobj,} typinfo,
+  {rttiobj,} typinfo, e_texture,
   SysUtils, Classes, GL, SDL2,
   MAPDEF, g_main, g_options,
   utils, hashtable, xparser;
@@ -114,7 +58,7 @@ var
   msY: Integer = -666;
   msB: Word = 0; // button state
   kbS: Word = 0; // keyboard modifiers state
-  showGrid: Boolean = false;
+  showGrid: Boolean = {$IF DEFINED(D2F_DEBUG)}false{$ELSE}false{$ENDIF};
   showMonsInfo: Boolean = false;
   showMonsLOS2Plr: Boolean = false;
   showAllMonsCells: Boolean = false;
@@ -122,249 +66,13 @@ var
   showLayersWindow: Boolean = false;
   showOutlineWindow: Boolean = false;
   showTriggers: Boolean = {$IF DEFINED(D2F_DEBUG)}false{$ELSE}false{$ENDIF};
-
-// ////////////////////////////////////////////////////////////////////////// //
-{$INCLUDE g_holmes.inc}
-{$INCLUDE g_holmes_ui.inc}
+  showTraceBox: Boolean = {$IF DEFINED(D2F_DEBUG)}false{$ELSE}false{$ENDIF};
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-// any mods = 255: nothing was defined
-function parseModKeys (const s: AnsiString; out kmods: Byte; out mbuts: Byte): AnsiString;
-var
-  pos, epos: Integer;
-begin
-  kmods := 255;
-  mbuts := 255;
-  pos := 1;
-  //while (pos <= Length(s)) and (s[pos] <= ' ') do Inc(pos);
-  if (pos < Length(s)) and ((s[pos] = '+') or (s[pos] = '-') or (s[pos] = '*')) then Inc(pos);
-  while (pos < Length(s)) do
-  begin
-    if (Length(s)-pos >= 2) and (s[pos+1] = '-') then
-    begin
-      case s[pos] of
-        'C', 'c': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModCtrl; Inc(pos, 2); continue; end;
-        'M', 'm': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModAlt; Inc(pos, 2); continue; end;
-        'S', 's': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModShift; Inc(pos, 2); continue; end;
-      end;
-      break;
-    end;
-    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
-    begin
-      case s[pos] of
-        'L', 'l': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Left; Inc(pos, 4); continue; end;
-        'R', 'r': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Right; Inc(pos, 4); continue; end;
-        'M', 'm': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Middle; Inc(pos, 4); continue; end;
-      end;
-      break;
-    end;
-    break;
-  end;
-  epos := Length(s)+1;
-  while (epos > pos) and (s[epos-1] <= ' ') do Dec(epos);
-  if (epos > pos) then result := Copy(s, pos, epos-pos) else result := '';
-end;
-
-
-operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
-var
-  f: Integer;
-  kmods: Byte = 255;
-  mbuts: Byte = 255;
-  kname: AnsiString;
-begin
-  result := false;
-  if (Length(s) > 0) then
-  begin
-         if (s[1] = '+') then begin if (ev.kind <> ev.Press) then exit; end
-    else if (s[1] = '-') then begin if (ev.kind <> ev.Release) then exit; end
-    else if (s[1] = '*') then begin end
-    else if (ev.kind <> ev.Press) then exit;
-  end;
-  kname := parseModKeys(s, kmods, mbuts);
-  if (kmods = 255) then kmods := 0;
-  if (ev.kstate <> kmods) then exit;
-  if (mbuts <> 255) and (ev.bstate <> mbuts) then exit;
-  for f := 1 to High(e_KeyNames) do
-  begin
-    if (CompareText(kname, e_KeyNames[f]) = 0) then
-    begin
-      result := (ev.scan = f);
-      exit;
-    end;
-  end;
-end;
-
-
-operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
-begin
-  result := (ev = s);
-end;
-
-
-operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
-var
-  kmods: Byte = 255;
-  mbuts: Byte = 255;
-  kname: AnsiString;
-  but: Integer = -1;
-begin
-  result := false;
-
-  if (Length(s) > 0) then
-  begin
-         if (s[1] = '+') then begin if (ev.kind <> ev.Press) then exit; end
-    else if (s[1] = '-') then begin if (ev.kind <> ev.Release) then exit; end
-    else if (s[1] = '*') then begin if (ev.kind <> ev.Motion) then exit; end
-    else if (ev.kind <> ev.Press) then exit;
-  end;
-
-  kname := parseModKeys(s, kmods, mbuts);
-       if (CompareText(kname, 'LMB') = 0) then but := THMouseEvent.Left
-  else if (CompareText(kname, 'RMB') = 0) then but := THMouseEvent.Right
-  else if (CompareText(kname, 'MMB') = 0) then but := THMouseEvent.Middle
-  else if (CompareText(kname, 'None') = 0) then but := 0
-  else exit;
-
-  //conwritefln('s=[%s]; kname=[%s]; kmods=%s; mbuts=%s; but=%s', [s, kname, kmods, mbuts, but]);
-
-  if (mbuts = 255) then mbuts := 0;
-  if (kmods = 255) then kmods := 0;
-  if (ev.kstate <> kmods) then exit;
-
-       if (ev.kind = ev.Press) then mbuts := mbuts or but
-  else if (ev.kind = ev.Release) then mbuts := mbuts and (not but);
-
-  //conwritefln('  ev.bstate=%s; ev.but=%s; mbuts=%s', [ev.bstate, ev.but, mbuts]);
-
-  result := (ev.bstate = mbuts) and (ev.but = but);
-end;
-
-
-operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
-begin
-  result := (ev = s);
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-function typeKind2Str (t: TTypeKind): AnsiString;
-begin
-  case t of
-    tkUnknown: result := 'Unknown';
-    tkInteger: result := 'Integer';
-    tkChar: result := 'Char';
-    tkEnumeration: result := 'Enumeration';
-    tkFloat: result := 'Float';
-    tkSet: result := 'Set';
-    tkMethod: result := 'Method';
-    tkSString: result := 'SString';
-    tkLString: result := 'LString';
-    tkAString: result := 'AString';
-    tkWString: result := 'WString';
-    tkVariant: result := 'Variant';
-    tkArray: result := 'Array';
-    tkRecord: result := 'Record';
-    tkInterface: result := 'Interface';
-    tkClass: result := 'Class';
-    tkObject: result := 'Object';
-    tkWChar: result := 'WChar';
-    tkBool: result := 'Bool';
-    tkInt64: result := 'Int64';
-    tkQWord: result := 'QWord';
-    tkDynArray: result := 'DynArray';
-    tkInterfaceRaw: result := 'InterfaceRaw';
-    tkProcVar: result := 'ProcVar';
-    tkUString: result := 'UString';
-    tkUChar: result := 'UChar';
-    tkHelper: result := 'Helper';
-    tkFile: result := 'File';
-    tkClassRef: result := 'ClassRef';
-    tkPointer: result := 'Pointer';
-    else result := '<unknown>';
-  end;
-end;
-
-
-procedure dumpPublishedProperties (obj: TObject);
-var
-  pt: PTypeData;
-  pi: PTypeInfo;
-  i, j: Integer;
-  pp: PPropList;
-begin
-  if (obj = nil) then exit;
-  e_LogWritefln('Object of type ''%s'':', [obj.ClassName]);
-  pi := obj.ClassInfo;
-  pt := GetTypeData(pi);
-  e_LogWritefln('property count: %s', [pt.PropCount]);
-  GetMem(pp, pt^.PropCount*sizeof(Pointer));
-  try
-    j := GetPropList(pi, [tkInteger, tkBool, tkSString, tkLString, tkAString, tkSet, tkEnumeration], pp);
-    //e_LogWritefln('ordinal property count: %s', [j]);
-    for i := 0 to j-1 do
-    begin
-      if (typinfo.PropType(obj, pp^[i].name) in [tkSString, tkLString, tkAString]) then
-      begin
-        e_LogWritefln('  #%s: <%s>; type: %s; value: <%s>', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetStrProp(obj, pp^[i])]);
-      end
-      else if (typinfo.PropType(obj, pp^[i].name) = tkSet) then
-      begin
-        e_LogWritefln('  #%s: <%s>; type: %s; value: %s', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetSetProp(obj, pp^[i], true)]);
-      end
-      else if (typinfo.PropType(obj, pp^[i].name) = tkEnumeration) then
-      begin
-        e_LogWritefln('  #%s: <%s>; type: %s; value: <%s>', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetEnumProp(obj, pp^[i])]);
-      end
-      else
-      begin
-        e_LogWritefln('  #%s: <%s>; type: %s; value: %s', [i+1, pp^[i].name, typeKind2Str(typinfo.PropType(obj, pp^[i].name)), GetOrdProp(obj, pp^[i])]);
-      end;
-    end;
-  finally
-    FreeMem(pp);
-  end;
-end;
-
+{$INCLUDE g_holmes.inc}
+{$INCLUDE g_holmes_ol.inc} // outliner
 
-//FIXME: autogenerate
-function trigType2Str (ttype: Integer): AnsiString;
-begin
-  result := '<unknown>';
-  case ttype of
-    TRIGGER_NONE: result := 'none';
-    TRIGGER_EXIT: result := 'exit';
-    TRIGGER_TELEPORT: result := 'teleport';
-    TRIGGER_OPENDOOR: result := 'opendoor';
-    TRIGGER_CLOSEDOOR: result := 'closedoor';
-    TRIGGER_DOOR: result := 'door';
-    TRIGGER_DOOR5: result := 'door5';
-    TRIGGER_CLOSETRAP: result := 'closetrap';
-    TRIGGER_TRAP: result := 'trap';
-    TRIGGER_PRESS: result := 'press';
-    TRIGGER_SECRET: result := 'secret';
-    TRIGGER_LIFTUP: result := 'liftup';
-    TRIGGER_LIFTDOWN: result := 'liftdown';
-    TRIGGER_LIFT: result := 'lift';
-    TRIGGER_TEXTURE: result := 'texture';
-    TRIGGER_ON: result := 'on';
-    TRIGGER_OFF: result := 'off';
-    TRIGGER_ONOFF: result := 'onoff';
-    TRIGGER_SOUND: result := 'sound';
-    TRIGGER_SPAWNMONSTER: result := 'spawnmonster';
-    TRIGGER_SPAWNITEM: result := 'spawnitem';
-    TRIGGER_MUSIC: result := 'music';
-    TRIGGER_PUSH: result := 'push';
-    TRIGGER_SCORE: result := 'score';
-    TRIGGER_MESSAGE: result := 'message';
-    TRIGGER_DAMAGE: result := 'damage';
-    TRIGGER_HEALTH: result := 'health';
-    TRIGGER_SHOT: result := 'shot';
-    TRIGGER_EFFECT: result := 'effect';
-    TRIGGER_SCRIPT: result := 'script';
-  end;
-end;
 
 // ////////////////////////////////////////////////////////////////////////// //
 {$INCLUDE g_holmes_cmd.inc}
@@ -596,50 +304,30 @@ end;
 
 procedure toggleLayersWindow (arg: Integer=-1);
 begin
-  showLayersWindow := not showLayersWindow;
+  if (arg < 0) then showLayersWindow := not showLayersWindow else showLayersWindow := (arg > 0);
   toggleLayersWindowCB(nil, 0);
 end;
 
 procedure toggleOutlineWindow (arg: Integer=-1);
 begin
-  showOutlineWindow := not showOutlineWindow;
+  if (arg < 0) then showOutlineWindow := not showOutlineWindow else showOutlineWindow := (arg > 0);
   toggleOutlineWindowCB(nil, 0);
 end;
 
 procedure toggleHelpWindow (arg: Integer=-1);
 begin
   if (winHelp = nil) then createHelpWindow();
-  if not uiVisibleWindow(winHelp) then uiAddWindow(winHelp) else uiRemoveWindow(winHelp);
+       if (arg < 0) then begin if not uiVisibleWindow(winHelp) then uiAddWindow(winHelp) else uiRemoveWindow(winHelp); end
+  else if (arg = 0) then begin if uiVisibleWindow(winHelp) then uiRemoveWindow(winHelp); end
+  else begin if not uiVisibleWindow(winHelp) then uiAddWindow(winHelp); end
 end;
 
 procedure toggleOptionsWindow (arg: Integer=-1);
 begin
   if (winOptions = nil) then createOptionsWindow();
-  if not uiVisibleWindow(winOptions) then uiAddWindow(winOptions) else uiRemoveWindow(winOptions);
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-procedure g_Holmes_VidModeChanged ();
-begin
-  e_WriteLog(Format('Holmes: videomode changed: %dx%d', [gScreenWidth, gScreenHeight]), MSG_NOTIFY);
-  // texture space is possibly lost here, idc
-  curtexid := 0;
-  font6texid := 0;
-  font8texid := 0;
-  prfont6texid := 0;
-  prfont8texid := 0;
-  //createCursorTexture();
-end;
-
-procedure g_Holmes_WindowFocused ();
-begin
-  msB := 0;
-  kbS := 0;
-end;
-
-procedure g_Holmes_WindowBlured ();
-begin
+       if (arg < 0) then begin if not uiVisibleWindow(winOptions) then uiAddWindow(winOptions) else uiRemoveWindow(winOptions); end
+  else if (arg = 0) then begin if uiVisibleWindow(winOptions) then uiRemoveWindow(winOptions); end
+  else begin if not uiVisibleWindow(winOptions) then uiAddWindow(winOptions); end
 end;
 
 
@@ -651,13 +339,19 @@ var
   laserSet: Boolean = false;
   laserX0, laserY0, laserX1, laserY1: Integer;
   monMarkedUID: Integer = -1;
+  platMarkedGUID: Integer = -1;
 
 
-procedure g_Holmes_plrView (viewPortX, viewPortY, viewPortW, viewPortH: Integer);
+procedure g_Holmes_plrViewPos (viewPortX, viewPortY: Integer);
 begin
   vpSet := true;
   vpx := viewPortX;
   vpy := viewPortY;
+end;
+
+procedure g_Holmes_plrViewSize (viewPortW, viewPortH: Integer);
+begin
+  vpSet := true;
   vpw := viewPortW;
   vph := viewPortH;
 end;
@@ -687,6 +381,7 @@ begin
 end;
 
 
+{$IFDEF HOLMES_OLD_OUTLINES}
 var
   edgeBmp: array of Byte = nil;
 
@@ -889,6 +584,151 @@ begin
   if g_ol_rlayer_fore then doWallsOld(gRenderForegrounds, PANEL_FORE, 210, 210, 210);
 end;
 
+{$ELSE}
+var
+  oliner: TOutliner = nil;
+
+procedure drawOutlines ();
+var
+  r, g, b: Integer;
+
+  procedure clearOliner ();
+  begin
+    //if (oliner <> nil) and ((oliner.height <> vph+2) or (oliner.width <> vpw+2)) then begin oliner.Free(); oliner := nil; end;
+    if (oliner = nil) then oliner := TOutliner.Create(vpw+2, vph+2) else oliner.setup(vpw+2, vph+2);
+  end;
+
+  procedure drawOutline (ol: TOutliner; sx, sy: Integer);
+    procedure xline (x0, x1, y: Integer);
+    var
+      x: Integer;
+    begin
+      if (g_dbg_scale < 1.0) then
+      begin
+        glBegin(GL_POINTS);
+          for x := x0 to x1 do glVertex2f(sx+x+0.375, sy+y+0.375);
+        glEnd();
+      end
+      else
+      begin
+        glBegin(GL_QUADS);
+          glVertex2f(sx+x0+0, sy+y+0);
+          glVertex2f(sx+x1+1, sy+y+0);
+          glVertex2f(sx+x1+1, sy+y+1);
+          glVertex2f(sx+x0+0, sy+y+1);
+        glEnd();
+      end;
+    end;
+  var
+    y: Integer;
+    sp: TOutliner.TSpanX;
+  begin
+    if (ol = nil) then exit;
+    glPointSize(1);
+    glDisable(GL_POINT_SMOOTH);
+    for y := 0 to ol.height-1 do
+    begin
+      for sp in ol.eachSpanAtY(y) do
+      begin
+        if (g_dbg_scale <= 1.0) then
+        begin
+          glBegin(GL_POINTS);
+            glVertex2f(sx+sp.x0+0.375, sy+y+0.375);
+            glVertex2f(sx+sp.x1+0.375, sy+y+0.375);
+          glEnd();
+        end
+        else
+        begin
+          glBegin(GL_QUADS);
+            glVertex2f(sx+sp.x0+0, sy+y+0);
+            glVertex2f(sx+sp.x0+1, sy+y+0);
+            glVertex2f(sx+sp.x0+1, sy+y+1);
+            glVertex2f(sx+sp.x0+0, sy+y+1);
+
+            glVertex2f(sx+sp.x1+0, sy+y+0);
+            glVertex2f(sx+sp.x1+1, sy+y+0);
+            glVertex2f(sx+sp.x1+1, sy+y+1);
+            glVertex2f(sx+sp.x1+0, sy+y+1);
+          glEnd();
+        end;
+      end;
+      for sp in ol.eachSpanEdgeAtY(y, -1) do
+      begin
+        xline(sp.x0, sp.x1, y);
+        {
+        glBegin(GL_QUADS);
+          glVertex2f(sx+sp.x0+0, sy+y+0);
+          glVertex2f(sx+sp.x1+1, sy+y+0);
+          glVertex2f(sx+sp.x1+1, sy+y+1);
+          glVertex2f(sx+sp.x0+0, sy+y+1);
+        glEnd();
+        }
+      end;
+      for sp in ol.eachSpanEdgeAtY(y, +1) do
+      begin
+        xline(sp.x0, sp.x1, y);
+        {
+        glBegin(GL_QUADS);
+          glVertex2f(sx+sp.x0+0, sy+y+0);
+          glVertex2f(sx+sp.x1+1, sy+y+0);
+          glVertex2f(sx+sp.x1+1, sy+y+1);
+          glVertex2f(sx+sp.x0+0, sy+y+1);
+        glEnd();
+        }
+      end;
+    end;
+  end;
+
+  procedure doWallsOld (parr: array of TPanel; ptype: Word; ar, ag, ab: Integer);
+  var
+    f: Integer;
+    pan: TPanel;
+  begin
+    r := ar;
+    g := ag;
+    b := ab;
+    if g_ol_nice then clearOliner();
+    for f := 0 to High(parr) do
+    begin
+      pan := parr[f];
+      if (pan = nil) or not pan.Enabled or (pan.Width < 1) or (pan.Height < 1) then continue;
+      if ((pan.PanelType and ptype) = 0) then continue;
+      if (pan.X > vpx+vpw+41) or (pan.Y > vpy+vph+41) then continue;
+      if (pan.X+pan.Width < vpx-41) then continue;
+      if (pan.Y+pan.Height < vpy-41) then continue;
+      if g_ol_nice then
+      begin
+        oliner.addRect(pan.X-(vpx+1), pan.Y-(vpy+1), pan.Width, pan.Height);
+      end;
+      if g_ol_fill_walls then
+      begin
+        fillRect(pan.X, pan.Y, pan.Width, pan.Height, r, g, b);
+      end
+      else if not g_ol_nice then
+      begin
+        drawRect(pan.X, pan.Y, pan.Width, pan.Height, r, g, b);
+      end;
+    end;
+    if g_ol_nice then
+    begin
+      glColor4f(r/255.0, g/255.0, b/255.0, 1.0);
+      drawOutline(oliner, vpx+1, vpy+1);
+    end;
+  end;
+
+begin
+  if (vpw < 2) or (vph < 2) then exit;
+  if g_ol_rlayer_back then doWallsOld(gRenderBackgrounds, PANEL_BACK, 255, 127, 0);
+  if g_ol_rlayer_step then doWallsOld(gSteps, PANEL_STEP, 192, 192, 192);
+  if g_ol_rlayer_wall then doWallsOld(gWalls, PANEL_WALL, 255, 255, 255);
+  if g_ol_rlayer_door then doWallsOld(gWalls, PANEL_OPENDOOR or PANEL_CLOSEDOOR, 0, 255, 0);
+  if g_ol_rlayer_acid1 then doWallsOld(gAcid1, PANEL_ACID1, 255, 0, 0);
+  if g_ol_rlayer_acid2 then doWallsOld(gAcid2, PANEL_ACID2, 198, 198, 0);
+  if g_ol_rlayer_water then doWallsOld(gWater, PANEL_WATER, 0, 255, 255);
+  if g_ol_rlayer_fore then doWallsOld(gRenderForegrounds, PANEL_FORE, 210, 210, 210);
+end;
+{$ENDIF}
+
 
 procedure plrDebugDraw ();
   procedure drawTileGrid ();
@@ -906,6 +746,50 @@ procedure plrDebugDraw ();
     end;
   end;
 
+  procedure drawAwakeCells ();
+  var
+    x, y: Integer;
+  begin
+    for y := 0 to (mapGrid.gridHeight div mapGrid.tileSize) do
+    begin
+      for x := 0 to (mapGrid.gridWidth div mapGrid.tileSize) do
+      begin
+        if awmIsSetHolmes(x*mapGrid.tileSize+mapGrid.gridX0+1, y*mapGrid.tileSize++mapGrid.gridY0+1) then
+        begin
+          fillRect(x*mapGrid.tileSize++mapGrid.gridX0, y*mapGrid.tileSize++mapGrid.gridY0, monsGrid.tileSize, monsGrid.tileSize, 128, 0, 128, 64);
+        end;
+      end;
+    end;
+  end;
+
+  procedure drawTraceBox ();
+  var
+    plr: TPlayer;
+    px, py, pw, ph: Integer;
+    pdx, pdy: Integer;
+    ex, ey: Integer;
+    pan: TPanel;
+  begin
+    if (Length(gPlayers) < 1) then exit;
+    plr := gPlayers[0];
+    if (plr = nil) then exit;
+    plr.getMapBox(px, py, pw, ph);
+    drawRect(px, py, pw, ph, 255, 0, 255, 200);
+    pdx := pmsCurMapX-(px+pw div 2);
+    pdy := pmsCurMapY-(py+ph div 2);
+    drawLine(px+pw div 2, py+ph div 2, px+pw div 2+pdx, py+ph div 2+pdy, 255, 0, 255, 200);
+    pan := mapGrid.traceBox(ex, ey, px, py, pw, ph, pdx, pdy, nil, GridTagObstacle);
+    if (pan = nil) then
+    begin
+      drawRect(px+pdx, py+pdy, pw, ph, 255, 255, 255, 180);
+    end
+    else
+    begin
+      drawRect(px+pdx, py+pdy, pw, ph, 255, 255, 0, 180);
+    end;
+    drawRect(ex, ey, pw, ph, 255, 127, 0, 180);
+  end;
+
   procedure hilightCell (cx, cy: Integer);
   begin
     fillRect(cx, cy, monsGrid.tileSize, monsGrid.tileSize, 0, 128, 0, 64);
@@ -914,6 +798,8 @@ procedure plrDebugDraw ();
   procedure hilightCell1 (cx, cy: Integer);
   begin
     //e_WriteLog(Format('h1: (%d,%d)', [cx, cy]), MSG_NOTIFY);
+    cx := cx and (not (monsGrid.tileSize-1));
+    cy := cy and (not (monsGrid.tileSize-1));
     fillRect(cx, cy, monsGrid.tileSize, monsGrid.tileSize, 255, 255, 0, 92);
   end;
 
@@ -984,7 +870,7 @@ procedure plrDebugDraw ();
       mon.getMapBox(mx, my, mw, mh);
       drawLine(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, 255, 0, 0, 255);
       {$IF DEFINED(D2F_DEBUG)}
-      //mapGrid.dbgRayTraceTileHitCB := hilightCell1;
+      mapGrid.dbgRayTraceTileHitCB := hilightCell1;
       {$ENDIF}
       if (g_Map_traceToNearestWall(mx+mw div 2, my+mh div 2, emx+emw div 2, emy+emh div 2, @ex, @ey) <> nil) then
       //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
@@ -992,7 +878,7 @@ procedure plrDebugDraw ();
         drawLine(mx+mw div 2, my+mh div 2, ex, ey, 0, 255, 0, 255);
       end;
       {$IF DEFINED(D2F_DEBUG)}
-      //mapGrid.dbgRayTraceTileHitCB := nil;
+      mapGrid.dbgRayTraceTileHitCB := nil;
       {$ENDIF}
     end;
 
@@ -1040,6 +926,17 @@ procedure plrDebugDraw ();
     monsGrid.forEachBodyCell(mon.proxyId, hilightCell);
   end;
 
+  procedure drawSelectedPlatformCells ();
+  var
+    pan: TPanel;
+  begin
+    if not showGrid then exit;
+    pan := g_Map_PanelByGUID(platMarkedGUID);
+    if (pan = nil) then exit;
+    mapGrid.forEachBodyCell(pan.proxyId, hilightCell);
+    drawRect(pan.x, pan.y, pan.width, pan.height, 0, 200, 0, 200);
+  end;
+
   procedure drawTrigger (var trig: TTrigger);
 
     procedure drawPanelDest (pguid: Integer);
@@ -1084,16 +981,16 @@ procedure plrDebugDraw ();
       TRIGGER_TEXTURE: begin end;
       TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF, TRIGGER_PRESS:
         begin
-          if (trig.trigData.trigTWidth > 0) and (trig.trigData.trigTHeight > 0) then
+          if (trig.trigDataRec.trigTWidth > 0) and (trig.trigDataRec.trigTHeight > 0) then
           begin
             fillRect(
-              trig.trigData.trigTX, trig.trigData.trigTY,
-              trig.trigData.trigTWidth, trig.trigData.trigTHeight,
+              trig.trigDataRec.trigTX, trig.trigDataRec.trigTY,
+              trig.trigDataRec.trigTWidth, trig.trigDataRec.trigTHeight,
               0, 255, 255, 42);
             drawLine(
               trig.trigCenter.x, trig.trigCenter.y,
-              trig.trigData.trigTX+trig.trigData.trigTWidth div 2,
-              trig.trigData.trigTY+trig.trigData.trigTHeight div 2,
+              trig.trigDataRec.trigTX+trig.trigDataRec.trigTWidth div 2,
+              trig.trigDataRec.trigTY+trig.trigDataRec.trigTHeight div 2,
               255, 0, 255, 220);
           end;
         end;
@@ -1121,41 +1018,87 @@ procedure plrDebugDraw ();
     for f := 0 to High(gTriggers) do drawTrigger(gTriggers[f]);
   end;
 
+  procedure drawGibsBoxes ();
+  var
+    f: Integer;
+    px, py, pw, ph: Integer;
+    gib: PGib;
+  begin
+    for f := 0 to High(gGibs) do
+    begin
+      gib := @gGibs[f];
+      if gib.alive then
+      begin
+        gib.getMapBox(px, py, pw, ph);
+        drawRect(px, py, pw, ph, 255, 0, 255);
+      end;
+    end;
+  end;
+
 var
+  scisave: TScissorSave;
   mon: TMonster;
   mx, my, mw, mh: Integer;
+  //pan: TPanel;
+  //ex, ey: Integer;
 begin
   if (gPlayer1 = nil) then exit;
 
-  glEnable(GL_SCISSOR_TEST);
-  glScissor(0, gWinSizeY-gPlayerScreenSize.Y-1, vpw, vph);
-
+  scisave.save(true); // enable scissoring
   glPushMatrix();
-  glScalef(g_dbg_scale, g_dbg_scale, 1.0);
-  glTranslatef(-vpx, -vpy, 0);
+  try
+    //glScissor(0, gWinSizeY-gPlayerScreenSize.Y-1, vpw, vph);
+    glScissor(0, gScreenHeight-gPlayerScreenSize.Y-1, gPlayerScreenSize.X, gPlayerScreenSize.Y);
 
-  if (showGrid) then drawTileGrid();
-  drawOutlines();
+    glScalef(g_dbg_scale, g_dbg_scale, 1.0);
+    glTranslatef(-vpx, -vpy, 0);
 
-  if (laserSet) then g_Mons_AlongLine(laserX0, laserY0, laserX1, laserY1, monsCollector, true);
+    if (showGrid) then drawTileGrid();
+    drawOutlines();
 
-  if (monMarkedUID <> -1) then
-  begin
-    mon := g_Monsters_ByUID(monMarkedUID);
-    if (mon <> nil) then
+    if (laserSet) then g_Mons_AlongLine(laserX0, laserY0, laserX1, laserY1, monsCollector, true);
+
+    if (monMarkedUID <> -1) then
     begin
-      mon.getMapBox(mx, my, mw, mh);
-      e_DrawQuad(mx, my, mx+mw-1, my+mh-1, 255, 0, 0, 30);
-      drawMonsterInfo(mon);
+      mon := g_Monsters_ByUID(monMarkedUID);
+      if (mon <> nil) then
+      begin
+        mon.getMapBox(mx, my, mw, mh);
+        e_DrawQuad(mx, my, mx+mw-1, my+mh-1, 255, 0, 0, 30);
+        drawMonsterInfo(mon);
+      end;
     end;
-  end;
 
-  if showAllMonsCells then g_Mons_ForEach(highlightAllMonsterCells);
-  if showTriggers then drawTriggers();
+    if showAllMonsCells and showGrid then g_Mons_ForEach(highlightAllMonsterCells);
+    if showTriggers then drawTriggers();
+    if showGrid then drawSelectedPlatformCells();
 
-  glPopMatrix();
+    //drawAwakeCells();
 
-  glDisable(GL_SCISSOR_TEST);
+    if showTraceBox then drawTraceBox();
+
+    //drawGibsBoxes();
+
+
+    //pan := g_Map_traceToNearest(16, 608, 16, 8, (GridTagObstacle or GridTagLiquid), @ex, @ey);
+    (*
+    {$IF DEFINED(D2F_DEBUG)}
+    mapGrid.dbgRayTraceTileHitCB := hilightCell1;
+    {$ENDIF}
+    pan := mapGrid.traceRay(ex, ey, 16, 608, 16, 8, nil, (GridTagObstacle or GridTagLiquid));
+    if (pan <> nil) then writeln('end=(', ex, ',', ey, ')');
+    {$IF DEFINED(D2F_DEBUG)}
+    mapGrid.dbgRayTraceTileHitCB := nil;
+    {$ENDIF}
+
+    pan := g_Map_PanelAtPoint(16, 608, (GridTagObstacle or GridTagLiquid));
+    if (pan <> nil) then writeln('hit!');
+    *)
+
+  finally
+    glPopMatrix();
+    scisave.restore();
+  end;
 
   if showMapCurPos then drawText8(4, gWinSizeY-10, Format('mappos:(%d,%d)', [pmsCurMapX, pmsCurMapY]), 255, 255, 0);
 end;
@@ -1166,17 +1109,20 @@ function g_Holmes_MouseEvent (var ev: THMouseEvent): Boolean;
 var
   he: THMouseEvent;
 begin
+  if g_Game_IsNet then begin result := false; exit; end;
+  if not g_holmes_enabled then begin result := false; exit; end;
+
   holmesInitCommands();
   holmesInitBinds();
   result := true;
-  msX := trunc(ev.x/g_holmes_ui_scale);
-  msY := trunc(ev.y/g_holmes_ui_scale);
+  msX := ev.x;
+  msY := ev.y;
   msB := ev.bstate;
   kbS := ev.kstate;
   msB := msB;
   he := ev;
-  he.x := trunc(he.x/g_holmes_ui_scale);
-  he.y := trunc(he.y/g_holmes_ui_scale);
+  he.x := he.x;
+  he.y := he.y;
   if not uiMouseEvent(he) then plrDebugMouse(he);
 end;
 
@@ -1195,6 +1141,9 @@ var
   end;
 
 begin
+  if g_Game_IsNet then begin result := false; exit; end;
+  if not g_holmes_enabled then begin result := false; exit; end;
+
   holmesInitCommands();
   holmesInitBinds();
   result := false;
@@ -1209,7 +1158,7 @@ begin
   if uiKeyEvent(ev) then begin result := true; exit; end;
   if keybindExecute(ev) then begin result := true; exit; end;
   // press
-  if (ev.kind = THKeyEvent.Press) then
+  if (ev.press) then
   begin
     {$IF DEFINED(D2F_DEBUG)}
     // C-UP, C-DOWN, C-LEFT, C-RIGHT: trace 10 pixels from cursor in the respective direction
@@ -1245,6 +1194,8 @@ end;
 // ////////////////////////////////////////////////////////////////////////// //
 procedure g_Holmes_Draw ();
 begin
+  if g_Game_IsNet then exit;
+
   {$IF not DEFINED(HEADLESS)}
   holmesInitCommands();
   holmesInitBinds();
@@ -1255,10 +1206,7 @@ begin
   glDisable(GL_SCISSOR_TEST);
   glDisable(GL_TEXTURE_2D);
 
-  if gGameOn then
-  begin
-    plrDebugDraw();
-  end;
+  if gGameOn then plrDebugDraw();
   {$ENDIF}
 
   laserSet := false;
@@ -1267,18 +1215,29 @@ end;
 
 procedure g_Holmes_DrawUI ();
 begin
+  if g_Game_IsNet then exit;
+  if not g_holmes_enabled then exit;
   {$IF not DEFINED(HEADLESS)}
-  glPushMatrix();
-  glScalef(g_holmes_ui_scale, g_holmes_ui_scale, 1.0);
+  gGfxDoClear := false;
+  //if assigned(prerenderFrameCB) then prerenderFrameCB();
   uiDraw();
-  drawCursor();
-  glPopMatrix();
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  try
+    //glLoadIdentity();
+    if assigned(postrenderFrameCB) then postrenderFrameCB();
+  finally
+    glPopMatrix();
+  end;
   {$ENDIF}
 end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
 procedure bcOneMonsterThinkStep (); begin gmon_debug_think := false; gmon_debug_one_think_step := true; end;
+procedure bcOneMPlatThinkStep (); begin g_dbgpan_mplat_active := false; g_dbgpan_mplat_step := true; end;
+procedure bcMPlatToggle (); begin g_dbgpan_mplat_active := not g_dbgpan_mplat_active; end;
+
 procedure bcToggleMonsterInfo (arg: Integer=-1); begin if (arg < 0) then showMonsInfo := not showMonsInfo else showMonsInfo := (arg > 0); end;
 procedure bcToggleMonsterLOSPlr (arg: Integer=-1); begin if (arg < 0) then showMonsLOS2Plr := not showMonsLOS2Plr else showMonsLOS2Plr := (arg > 0); end;
 procedure bcToggleMonsterCells (arg: Integer=-1); begin if (arg < 0) then showAllMonsCells := not showAllMonsCells else showAllMonsCells := (arg > 0); end;
@@ -1287,6 +1246,20 @@ procedure bcToggleDrawTriggers (arg: Integer=-1); begin if (arg < 0) then showTr
 procedure bcToggleCurPos (arg: Integer=-1); begin if (arg < 0) then showMapCurPos := not showMapCurPos else showMapCurPos := (arg > 0); end;
 procedure bcToggleGrid (arg: Integer=-1); begin if (arg < 0) then showGrid := not showGrid else showGrid := (arg > 0); end;
 
+procedure bcMonsterSpawn (s: AnsiString);
+var
+  mon: TMonster;
+begin
+  if not gGameOn or g_Game_IsClient then
+  begin
+    conwriteln('cannot spawn monster in this mode');
+    exit;
+  end;
+  mon := g_Mons_SpawnAt(s, pmsCurMapX, pmsCurMapY);
+  if (mon = nil) then begin conwritefln('unknown monster id: ''%s''', [s]); exit; end;
+  monMarkedUID := mon.UID;
+end;
+
 procedure bcMonsterWakeup ();
 var
   mon: TMonster;
@@ -1310,11 +1283,15 @@ begin
   end;
 end;
 
+procedure dbgToggleTraceBox (arg: Integer=-1); begin if (arg < 0) then showTraceBox := not showTraceBox else showTraceBox := (arg > 0); end;
+
+procedure dbgToggleHolmesPause (arg: Integer=-1); begin if (arg < 0) then g_Game_HolmesPause(not gPauseHolmes) else g_Game_HolmesPause(arg > 0); end;
+
 procedure cbAtcurSelectMonster ();
   function monsAtDump (mon: TMonster; tag: Integer): Boolean;
   begin
     result := true; // stop
-    e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), MSG_NOTIFY);
+    e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), TMsgType.Notify);
     monMarkedUID := mon.UID;
     dumpPublishedProperties(mon);
   end;
@@ -1344,19 +1321,20 @@ procedure cbAtcurDumpMonsters ();
   function monsAtDump (mon: TMonster; tag: Integer): Boolean;
   begin
     result := false; // don't stop
-    e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), MSG_NOTIFY);
+    e_WriteLog(Format('monster #%d (UID:%u) (proxyid:%d)', [mon.arrIdx, mon.UID, mon.proxyId]), TMsgType.Notify);
   end;
 begin
-  e_WriteLog('===========================', MSG_NOTIFY);
+  e_WriteLog('===========================', TMsgType.Notify);
   monsGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, monsAtDump);
-  e_WriteLog('---------------------------', MSG_NOTIFY);
+  e_WriteLog('---------------------------', TMsgType.Notify);
 end;
 
 procedure cbAtcurDumpWalls ();
   function wallToggle (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop
-    e_LogWritefln('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]);
+    if (platMarkedGUID = -1) then platMarkedGUID := pan.guid;
+    e_LogWritefln('wall ''%s'' #%d(%d); enabled=%d (%d); (%d,%d)-(%d,%d)', [pan.mapId, pan.arrIdx, pan.proxyId, Integer(pan.Enabled), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.X, pan.Y, pan.Width, pan.Height]);
     dumpPublishedProperties(pan);
   end;
 var
@@ -1364,9 +1342,10 @@ var
   f: Integer;
   trig: PTrigger;
 begin
-  e_WriteLog('=== TOGGLE WALL ===', MSG_NOTIFY);
+  platMarkedGUID := -1;
+  e_WriteLog('=== TOGGLE WALL ===', TMsgType.Notify);
   mapGrid.forEachAtPoint(pmsCurMapX, pmsCurMapY, wallToggle, (GridTagWall or GridTagDoor));
-  e_WriteLog('--- toggle wall ---', MSG_NOTIFY);
+  e_WriteLog('--- toggle wall ---', TMsgType.Notify);
   if showTriggers then
   begin
     for f := 0 to High(gTriggers) do
@@ -1409,7 +1388,12 @@ begin
   cmdAdd('mon_info', bcToggleMonsterInfo, 'toggle monster info', 'monster control');
   cmdAdd('mon_los_plr', bcToggleMonsterLOSPlr, 'toggle monster LOS to player', 'monster control');
   cmdAdd('mon_cells', bcToggleMonsterCells, 'toggle "show all cells occupied by monsters" (SLOW!)', 'monster control');
-  cmdAdd('mon_wakeup', bcMonsterWakeup, 'toggle "show all cells occupied by monsters" (SLOW!)', 'monster control');
+  cmdAdd('mon_wakeup', bcMonsterWakeup, 'wake up selected monster', 'monster control');
+
+  cmdAdd('mon_spawn', bcMonsterSpawn, 'spawn monster', 'monster control');
+
+  cmdAdd('mplat_step', bcOneMPlatThinkStep, 'one mplat think step', 'mplat control');
+  cmdAdd('mplat_toggle', bcMPlatToggle, 'activate/deactivate moving platforms', 'mplat control');
 
   cmdAdd('plr_teleport', bcPlayerTeleport, 'teleport player', 'player control');
 
@@ -1421,6 +1405,10 @@ begin
   cmdAdd('atcur_dump_monsters', cbAtcurDumpMonsters, 'dump monsters in cell', 'monster control');
   cmdAdd('atcur_dump_walls', cbAtcurDumpWalls, 'dump walls in cell', 'wall control');
   cmdAdd('atcur_disable_walls', cbAtcurToggleWalls, 'disable walls', 'wall control');
+
+  cmdAdd('dbg_tracebox', dbgToggleTraceBox, 'test traceBox()', 'player control');
+
+  cmdAdd('hlm_pause', dbgToggleHolmesPause, '"Holmes" pause mode', 'game control');
 end;
 
 
@@ -1447,12 +1435,20 @@ begin
     keybindAdd('M-G', 'mon_cells');
     keybindAdd('M-A', 'mon_wakeup');
 
+    keybindAdd('M-P', 'mplat_step');
+    keybindAdd('M-O', 'mplat_toggle');
+
     keybindAdd('C-T', 'plr_teleport');
+    keybindAdd('M-T', 'dbg_tracebox');
 
     keybindAdd('C-P', 'dbg_curpos');
     keybindAdd('C-G', 'dbg_grid');
     keybindAdd('C-X', 'dbg_triggers');
 
+    keybindAdd('C-1', 'mon_spawn zombie');
+
+    keybindAdd('C-S-P', 'hlm_pause');
+
     // mouse
     msbindAdd('LMB', 'atcur_select_monster');
     msbindAdd('M-LMB', 'atcur_dump_monsters');
@@ -1499,6 +1495,21 @@ begin
 end;
 
 
+function onMouseEvent (var ev: THMouseEvent): Boolean;
 begin
-  conRegVar('hlm_ui_scale', @g_holmes_ui_scale, 0.01, 5.0, 'Holmes UI scale', '', false);
+  result := g_Holmes_MouseEvent(ev);
+end;
+
+function onKeyEvent (var ev: THKeyEvent): Boolean;
+begin
+  if not g_holmes_enabled then begin result := false; exit; end;
+  result := g_Holmes_keyEvent(ev);
+end;
+
+
+begin
+  evMouseCB := onMouseEvent;
+  evKeyCB := onKeyEvent;
+
+  conRegVar('hlm_ui_scale', @gh_ui_scale, 0.01, 5.0, 'Holmes UI scale', '', false);
 end.