{$INCLUDE Switches.inc}
unit Help;

interface

uses
  LazUTF8, Protocol, ScreenTools, BaseWin, StringTables, Math, LCLIntf, LCLType, Messages,
  SysUtils, Classes, ButtonB, PVSB, Types, Generics.Collections, IsoEngine,
  {$IFDEF DPI}Dpi.Graphics, Dpi.Controls, Dpi.Forms, Dpi.ExtCtrls, Dpi.Common,
  System.UITypes{$ELSE}
  Graphics, Controls, Forms, ExtCtrls{$ENDIF};

const
  MaxHist = 16;

  fJungle = 8; // Pseudo terrain

type
  { Link categories }
  TLinkCategory = (
    hkNoLink,
    hkAdv,
    hkImp,
    hkTer,
    hkFeature ,
    hkInternet,
    hkModel,
    hkMisc,
    hkText);

  { Link indices for category hkMisc }
  TMiscLinkIndex = (
    miscMain,
    miscCredits,
    miscGovList,
    miscJobList,
    miscSearchResult);

  TTextFormat = (
    pkNormal,
    pkCaption,
    pkSmallIcon,
    pkBigIcon,
    pkAdvIcon,
    pkTer,
    pkBigTer,
    pkFeature,
    pkDot,
    pkNormal_Dot,
    pkDomain,
    pkSection,
    pkBigFeature,
    pkExp,
    pkAITStat,
    pkExternal,
    pkModel,
    pkNormal_64,
    pkIllu,
    pkLogo,
    pkTerImp,
    pkRightIcon,
    pkAdvIcon_AsPreq,
    pkSmallIcon_AsPreq,
    pkSpecialIcon,
    pkGov);

  { THyperText }

  THyperText = class(TStringList)
  public
    procedure AddLine(S: String = ''; Format: TTextFormat = pkNormal; Picpix: Integer = 0;
      LinkCategory: TLinkCategory = hkNoLink; LinkIndex: Integer = 0; CrossLink: Boolean = False);
    procedure LineFeed;
    procedure AppendList(Source: THyperText);
  end;

  { THistItem }

  THistItem = class
    Kind: TLinkCategory;
    No: Integer;
    Pos: Integer;
    SearchContent: string;
    procedure Assign(Source: THistItem);
  end;

  { THistItems }

  THistItems = class(TObjectList<THistItem>)
    function AddNew(Kind: TLinkCategory; No, Pos: Integer; SearchContent: string): THistItem;
  end;

  { THelpDlg }

  THelpDlg = class(TFramedDlg)
    CloseBtn: TButtonB;
    BackBtn: TButtonB;
    TopBtn: TButtonB;
    SearchBtn: TButtonB;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure FormPaint(Sender: TObject);
    procedure CloseBtnClick(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure BackBtnClick(Sender: TObject);
    procedure TopBtnClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure SearchBtnClick(Sender: TObject);
  private
    Kind: TLinkCategory;
    No: Integer;
    Sel: Integer;
    CaptionColor: Integer;
    hADVHELP, hIMPHELP, hFEATUREHELP, hGOVHELP, hSPECIALMODEL, hJOBHELP: Integer;
    SearchContent: string;
    NewSearchContent: string;
    CaptionFont: TFont;
    MainText: THyperText;
    SearchResult: THyperText;
    HelpText: TStringTable;
    ExtPic: TBitmap;
    TerrIcon: TBitmap;
    ScrollBar: TPVScrollbar;
    NoMap: TIsoMap;
    x0: array [-2..180] of Integer;
    procedure PaintTerrIcon(X, Y, xSrc, ySrc: Integer);
    procedure ScrollBarUpdate(Sender: TObject);
    procedure Line(ACanvas: TCanvas; I: Integer; Lit: Boolean);
    procedure Prepare(sbPos: Integer = 0);
    procedure ShowNewContentProcExecute(NewMode: TWindowMode; HelpContext: string);
    procedure WaterSign(x0, y0, iix: Integer);
    procedure Search(SearchString: string);
    procedure OnScroll(var Msg: TMessage); message WM_VSCROLL;
    procedure OnMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
  protected
    procedure OffscreenPaint; override;
    procedure DoOnResize; override;
  public
    HistItems: THistItems;
    Difficulty: Integer;
    procedure ClearHistory;
    procedure ShowNewContent(NewMode: TWindowMode; Category: TLinkCategory; Index: Integer);
    function TextIndex(Item: string): Integer;
  end;


implementation

uses
  {$IFDEF DPI}Dpi.PixelPointer,{$ELSE}PixelPointer,{$ENDIF}
  Directories, ClientTools, Term, Tribes, Inp, Messg, Global, KeyBindings;

{$R *.lfm}

type

  { THelpLineInfo }

  THelpLineInfo = class
    Format: TTextFormat;
    Picpix: Byte;
    Category: TLinkCategory;
    Index: Integer;
    CrossLink: Boolean;
    procedure Assign(Source: THelpLineInfo);
  end;

  TSeeAlso = record
    Kind: TLinkCategory;
    No: Integer;
    SeeKind: TLinkCategory;
    SeeNo: Integer;
  end;

const
  SeeAlso: array[0..13] of TSeeAlso = ((Kind: hkImp; no: imWalls; SeeKind: hkFeature;
    SeeNo: mcArtillery), (Kind: hkImp; no: imHydro; SeeKind: hkImp;
    SeeNo: woHoover), (Kind: hkImp; no: imWalls; SeeKind: hkImp;
    SeeNo: imGrWall), (Kind: hkImp; no: imHighways; SeeKind: hkAdv;
    SeeNo: adWheel), (Kind: hkImp; no: imCathedral; SeeKind: hkImp;
    SeeNo: woBach), (Kind: hkImp; no: imBank; SeeKind: hkImp; SeeNo: imStockEx),
    (Kind: hkImp; no: imShipComp; SeeKind: hkImp; SeeNo: imSpacePort),
    (Kind: hkImp; no: imShipPow; SeeKind: hkImp; SeeNo: imSpacePort),
    (Kind: hkImp; no: imShipHab; SeeKind: hkImp; SeeNo: imSpacePort),
    (Kind: hkFeature; no: mcSub; SeeKind: hkFeature; SeeNo: mcRadar),
    (Kind: hkFeature; no: mcDefense; SeeKind: hkAdv; SeeNo: adSteel),
    (Kind: hkFeature; no: mcSE; SeeKind: hkFeature; SeeNo: mcNP), (Kind: hkAdv;
    no: adWheel; SeeKind: hkImp; SeeNo: imHighways), (Kind: hkAdv; no: adSteel;
    SeeKind: hkFeature; SeeNo: mcDefense));

  TerrainHelp: array[0..13] of Integer = (fGrass, fGrass + 12,
    fPrairie, fForest, fJungle, fHills, fMountains, fSwamp, fTundra, fArctic,
    fDesert, 3 * 12 { DeadLands }, fShore, fOcean);

  JobHelp: array[0..7] of Integer = (jRoad, jRR, jCanal, jIrr,
    jFarm, jMine, jFort, jBase);

{ THelpLineInfo }

procedure THelpLineInfo.Assign(Source: THelpLineInfo);
begin
  Format := Source.Format;
  PicPix := Source.PicPix;
  Category := Source.Category;
  Index := Source.Index;
end;

{ THistItem }

procedure THistItem.Assign(Source: THistItem);
begin
  Kind := Source.Kind;
  No := Source.No;
  Pos := Source.Pos;
  SearchContent := Source.SearchContent;
end;

{ THistItems }

function THistItems.AddNew(Kind: TLinkCategory; No, Pos: Integer; SearchContent: string
  ): THistItem;
begin
  Result := THistItem.Create;
  Result.Kind := Kind;
  Result.No := No;
  Result.Pos := Pos;
  Result.SearchContent := SearchContent;
  Add(Result);
end;

procedure THyperText.AddLine(S: String; Format: TTextFormat; Picpix: Integer;
  LinkCategory: TLinkCategory = hkNoLink; LinkIndex: Integer = 0;
  CrossLink: Boolean = False);
var
  HelpLineInfo: THelpLineInfo;
begin
  HelpLineInfo := THelpLineInfo.Create;
  HelpLineInfo.Format := Format;
  HelpLineInfo.Picpix := Picpix;
  HelpLineInfo.Category := LinkCategory;
  HelpLineInfo.Index := LinkIndex;
  HelpLineInfo.CrossLink := CrossLink;
  AddObject(S, HelpLineInfo);
end;

procedure THyperText.LineFeed;
begin
  AddLine;
end;

procedure THyperText.AppendList(Source: THyperText);
var
  I: Integer;
  HelpLineInfo: THelpLineInfo;
begin
  for I := 0 to Source.Count - 1 do begin
    HelpLineInfo := THelpLineInfo.Create;
    HelpLineInfo.Assign(THelpLineInfo(Source.Objects[I]));
    AddObject(Source.Strings[I], HelpLineInfo);
  end;
end;

procedure THelpDlg.FormCreate(Sender: TObject);
begin
  inherited;
  NoMap := TIsoMap.Create;

  HistItems := THistItems.Create;

  CaptionLeft := BackBtn.Left + BackBtn.Width;
  CaptionRight := SearchBtn.Left;
  Inc(ModalFrameIndent, 29);
  MainText := THyperText.Create;
  MainText.OwnsObjects := True;
  SearchResult := THyperText.Create;
  SearchResult.OwnsObjects := True;
  ScrollBar := TPVScrollbar.Create(Self);
  ScrollBar.SetBorderSpacing(36, 9, 11);
  ScrollBar.OnUpdate := ScrollBarUpdate;

  HelpText := TStringTable.Create;
  HelpText.LoadFromFile(LocalizedFilePath('Help' + DirectorySeparator + 'Help.txt'));
  hADVHELP := HelpText.Gethandle('ADVHELP');
  hIMPHELP := HelpText.Gethandle('IMPHELP');
  hFEATUREHELP := HelpText.Gethandle('FEATUREHELP');
  hGOVHELP := HelpText.Gethandle('GOVHELP');
  hSPECIALMODEL := HelpText.Gethandle('SPECIALMODEL');
  hJOBHELP := HelpText.Gethandle('JOBHELP');

  CaptionFont := Font.Create;
  CaptionFont.Assign(UniFont[ftNormal]);
  CaptionFont.Style := CaptionFont.Style + [TFontStyle.fsItalic, TFontStyle.fsBold];
  InitButtons;

  TopBtn.Hint := Phrases['BTN_CONTENTS'];
  BackBtn.Hint := Phrases['BTN_BACK'];
  SearchBtn.Hint := Phrases['BTN_SEARCH'];

  ExtPic := TBitmap.Create;
  TerrIcon := TBitmap.Create;
  TerrIcon.PixelFormat := TPixelFormat.pf24bit;
  TerrIcon.SetSize(xSizeBig, ySizeBig);
  TerrIcon.Canvas.FillRect(0, 0, TerrIcon.Width, TerrIcon.Height);
  SearchContent := '';
  ShowNewContentProc := ShowNewContentProcExecute;
end;

procedure THelpDlg.ShowNewContentProcExecute(NewMode: TWindowMode;
  HelpContext: string);
begin
  ShowNewContent(NewMode, hkText, TextIndex(HelpContext));
end;

procedure THelpDlg.FormDestroy(Sender: TObject);
begin
  ShowNewContentProc := nil;
  FreeAndNil(ScrollBar);
  FreeAndNil(MainText);
  FreeAndNil(SearchResult);
  FreeAndNil(ExtPic);
  FreeAndNil(TerrIcon);
  FreeAndNil(HelpText);
  // FreeAndNil(CaptionFont);
  FreeAndNil(HistItems);
  FreeAndNil(NoMap);
end;

procedure THelpDlg.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
  if ScrollBar.ProcessMouseWheel(WheelDelta) then begin
    PaintBox1MouseMove(nil, [], MousePos.X - Left,
      MousePos.Y - Top);
  end;
end;

procedure THelpDlg.CloseBtnClick(Sender: TObject);
begin
  Close;
end;

procedure THelpDlg.OnScroll(var Msg: TMessage);
begin
  { TODO: Handled by MouseWheel event
  if ScrollBar.Process(Msg) then begin
    Sel := -1;
    SmartUpdateContent(True)
  end;
  }
end;

procedure THelpDlg.OnMouseLeave(var Msg: TMessage);
begin
  if Sel <> -1 then begin
    Line(Canvas, Sel, False);
    Sel := -1;
  end;
end;

procedure THelpDlg.ClearHistory;
begin
  HistItems.Clear;
end;

procedure THelpDlg.FormPaint(Sender: TObject);
begin
  inherited;
  Canvas.Font.Assign(UniFont[ftNormal]);
end;

procedure THelpDlg.Line(ACanvas: TCanvas; I: Integer; Lit: Boolean);
var
  TextColor: TColor;
  X, Y: Integer;
  TextSize: TSize;
  S: string;
  FirstLetter: string;
begin
  S := MainText[ScrollBar.Position + I];
  if S = '' then
    Exit;
  X := x0[I];
  Y := 2 + I * 24;
  if ACanvas = Canvas then
  begin
    X := X + SideFrame;
    Y := Y + WideFrame;
  end;
  if THelpLineInfo(MainText.Objects[ScrollBar.Position + I]).Format
    in [pkCaption, pkBigTer, pkRightIcon, pkBigFeature] then
  begin
    ACanvas.Font.Assign(CaptionFont);
    { ACanvas.Brush.Color := CaptionColor;
      ACanvas.FillRect(Rect(X, I * 24, X + 24, I * 24 + 24));
      ACanvas.Brush.Color := $FFFFFF;
      ACanvas.FrameRect(Rect(X + 1, I * 24 + 1, X + 24 - 1, I * 24 + 24 - 1));
      ACanvas.Brush.Style := bsClear; }
    BitBltCanvas(ACanvas, X, Y - 4, 24, 24, HGrSystem.Data.Canvas, 1,
      146);
    FirstLetter := UTF8Copy(S, 1, 1);
    S := UTF8Copy(S, 2, MaxInt);
    BiColorTextOut(ACanvas, $FFFFFF, $7F007F, X + 10 - ACanvas.TextWidth(FirstLetter) div 2,
      Y - 3, FirstLetter);
    BiColorTextOut(ACanvas, CaptionColor, $7F007F, X + 24, Y - 3, S);
    ACanvas.Font.Assign(UniFont[ftNormal]);
  end
  else if THelpLineInfo(MainText.Objects[ScrollBar.Position + I]).Format = pkSection then
  begin
    ACanvas.Font.Assign(CaptionFont);
    BiColorTextOut(ACanvas, CaptionColor, $7F007F, X, Y - 3, S);
    ACanvas.Font.Assign(UniFont[ftNormal]);
  end
  else
  begin
    if (Kind = hkMisc) and (No = Integer(miscMain)) then
      ACanvas.Font.Assign(CaptionFont);
    TextColor := Colors.Canvas.Pixels[clkMisc, cliPaperText];
    if ACanvas = Canvas then
    begin
      TextSize.cx := BiColorTextWidth(ACanvas, S);
      TextSize.cy := ACanvas.TextHeight(S);
      if Y + TextSize.cy >= WideFrame + InnerHeight then
        TextSize.cy := WideFrame + InnerHeight - Y;
      FillSeamless(ACanvas, X, Y, TextSize.cx, TextSize.cy, -SideFrame,
        ScrollBar.Position * 24 - WideFrame, Paper);
    end;
    BiColorTextOut(ACanvas, TextColor, $7F007F, X, Y, S);
    if lit then
      with ACanvas do
      begin
        Assert(ACanvas = Canvas);
        Pen.Color := TextColor;
        MoveTo(X + 1, Y + TextSize.cy - 2);
        LineTo(X + TextSize.cx, Y + TextSize.cy - 2);
      end;
    if (Kind = hkMisc) and (No = Integer(miscMain)) then
      ACanvas.Font.Assign(UniFont[ftNormal]);
  end;
end;

procedure THelpDlg.WaterSign(x0, y0, iix: Integer);
const
  nHeaven = 28;
  MaxSum = 9 * 9 * 255 * 75 div 100;
var
  X, Y, dx, dy, xSrc, ySrc, Sum, xx: Integer;
  Heaven: array [0..nHeaven] of Integer;
  PaintPtr: TPixelPointer;
  CoalPtr: TPixelPointer;
  ImpPtr: array [-1..1] of TPixelPointer;
begin
  // assume eiffel tower has free common heaven
  for dy := 0 to nHeaven - 1 do
    Heaven[dy] := BigImp.Canvas.Pixels[woEiffel mod 7 * xSizeBig,
      (SystemIconLines + woEiffel div 7) * ySizeBig + dy];

  BigImp.BeginUpdate;
  Offscreen.BeginUpdate;
  xSrc := iix mod 7 * xSizeBig;
  ySrc := (iix div 7 + 1) * ySizeBig;
  PaintPtr := TPixelPointer.Create(OffScreen, ScaleToNative(x0), ScaleToNative(y0));
  CoalPtr := TPixelPointer.Create(Templates.Data, ScaleToNative(xCoal),
    ScaleToNative(yCoal));
  for dy := -1 to 1 do
    ImpPtr[dy] := TPixelPointer.Create(BigImp, ScaleToNative(xSrc), ScaleToNative(ySrc));
  for Y := 0 to ScaleToNative(ySizeBig) * 2 - 1 do begin
    if ((ScaleToNative(y0) + Y) >= 0) and ((ScaleToNative(y0) + Y) < ScaleToNative(InnerHeight)) then begin
      for dy := -1 to 1 do
        if ((Max(Y + ScaleToNative(dy), 0) shr 1) >= 0) and ((Max(Y + ScaleToNative(dy), 0) shr 1) < ScaleToNative(ySizeBig)) then
          ImpPtr[dy].SetXY(0, Max(Y + ScaleToNative(dy), 0) shr 1);
      for X := 0 to ScaleToNative(xSizeBig * 2) - 1 do begin
        Sum := 0;
        for dx := -1 to 1 do begin
          xx := Max((X + ScaleToNative(dx)), 0) shr 1;
          for dy := -1 to 1 do begin
            ImpPtr[dy].SetX(xx);
            if ((Y + ScaleToNative(dy)) shr 1 < 0) or
              ((Y + ScaleToNative(dy)) shr 1 >= ScaleToNative(ySizeBig)) or
              ((X + ScaleToNative(dx)) shr 1 < 0) or
              ((X + ScaleToNative(dx)) shr 1 >= ScaleToNative(xSizeBig)) or
              ((Y + ScaleToNative(dy)) shr 1 < ScaleToNative(nHeaven)) and
              (ImpPtr[dy].PixelB shl 16 + ImpPtr[dy].PixelG shl 8 +
              ImpPtr[dy].PixelR = Heaven[(ScaleFromNative(Y + ScaleToNative(dy))) shr 1]) then
              Sum := Sum + 9 * 255
            else
              Sum := Sum + ImpPtr[dy].PixelB + 5 * ImpPtr[dy].PixelG + 3 *
                ImpPtr[dy].PixelR;
          end;
        end;
        if Sum < MaxSum then begin // No saturation
          Sum := 1 shl 22 - (MaxSum - Sum) * (256 - CoalPtr.PixelB * 2);
          PaintPtr.PixelB := Min(PaintPtr.PixelB * Sum shr 22, 255);
          PaintPtr.PixelG := Min(PaintPtr.PixelG * Sum shr 22, 255);
          PaintPtr.PixelR := Min(PaintPtr.PixelR * Sum shr 22, 255);
        end;
        PaintPtr.NextPixel;
        CoalPtr.NextPixel;
      end;
    end;
    PaintPtr.NextLine;
    CoalPtr.NextLine;
  end;
  Offscreen.EndUpdate;
  BigImp.EndUpdate;
end;

procedure THelpDlg.PaintTerrIcon(X, Y, xSrc, ySrc: Integer);
begin
  with NoMap do begin
    Frame(OffScreen.Canvas, X - 1, Y - 1, X + xSizeBig, Y + ySizeBig,
      $000000, $000000);
    if 2 * yyt < 40 then begin
      Sprite(OffScreen, HGrTerrain, X, Y, 56, 2 * yyt, xSrc, ySrc);
      Sprite(OffScreen, HGrTerrain, X, Y + 2 * yyt, 56, 40 - 2 * yyt,
        xSrc, ySrc);
    end else
      Sprite(OffScreen, HGrTerrain, X, Y, 56, 40, xSrc, ySrc);
    Sprite(OffScreen, HGrTerrain, X, Y, xxt, yyt, xSrc + xxt, ySrc + yyt);
    Sprite(OffScreen, HGrTerrain, X, Y + yyt, xxt, 40 - yyt, xSrc + xxt, ySrc);
    Sprite(OffScreen, HGrTerrain, X + xxt, Y, 56 - xxt, yyt, xSrc, ySrc + yyt);
    Sprite(OffScreen, HGrTerrain, X + xxt, Y + yyt, 56 - xxt, 40 - yyt,
      xSrc, ySrc);
  end;
end;

procedure THelpDlg.OffscreenPaint;
var
  I, J, yl, srcno, ofs, cnt, Y: Integer;
  S: string;
  HelpLineInfo: THelpLineInfo;
begin
  inherited;
  CaptionColor := Colors.Canvas.Pixels[clkMisc, cliPaperCaption];
  FillSeamless(OffScreen.Canvas, 0, 0, InnerWidth, InnerHeight, 0,
    ScrollBar.Position * 24, Paper);
  with OffScreen.Canvas do
  begin
    Font.Assign(UniFont[ftNormal]);
    for I := -ScrollBar.Position to InnerHeight div 24 do
      if ScrollBar.Position + I < MainText.Count then
      begin
        HelpLineInfo := THelpLineInfo(MainText.Objects[ScrollBar.Position + I]);
        if HelpLineInfo.Format = pkExternal then
        begin
          yl := ExtPic.Height;
          if 4 + I * 24 + yl > InnerHeight then
            yl := InnerHeight - (4 + I * 24);
          BitBltBitmap(OffScreen, 8, 4 + I * 24, ExtPic.Width, yl, ExtPic, 0, 0);
        end;
      end;
    for I := -2 to InnerHeight div 24 do
      if (ScrollBar.Position + I >= 0) and (ScrollBar.Position + I < MainText.Count) then
      begin
        HelpLineInfo := THelpLineInfo(MainText.Objects[ScrollBar.Position + I]);
        if (HelpLineInfo.Category <> hkNoLink) or (HelpLineInfo.Index <> 0) then
        begin
          if (Kind = hkMisc) and (No = Integer(miscSearchResult)) then
            Sprite(OffScreen, HGrSystem, 18, 9 + I * 24, 8, 8, 90, 16)
          else if HelpLineInfo.Format in [pkSmallIcon_AsPreq, pkAdvIcon_AsPreq]
          then
            Sprite(OffScreen, HGrSystem, 12, I * 24 + 5, 14, 14, 65, 20)
          else if HelpLineInfo.CrossLink then
            Sprite(OffScreen, HGrSystem, 12, I * 24 + 5, 14, 14, 80, 1)
          else if not ((Kind = hkMisc) and (No = Integer(miscMain))) then
            Sprite(OffScreen, HGrSystem, 10, I * 24 + 6, 14, 14, 65, 1);
          x0[I] := 24;
        end
        else
          x0[I] := 0;
        case HelpLineInfo.Format of
          pkLogo:
            begin
              Server(sGetVersion, 0, 0, J);
              S := Format('%d.%d.%d', [J shr 16 and $FF, J shr 8 and $FF,
                J and $FF]);
              PaintLogo(OffScreen.Canvas, (InnerWidth - 122) div 2, I * 24 + 1,
                HGrSystem.Data.Canvas.Pixels[95, 1], $000000);
              Font.Assign(UniFont[ftSmall]);
              BiColorTextOut(OffScreen.Canvas, $000000, $7F007F,
                (InnerWidth - TextWidth(S)) div 2, I * 24 + 26, S);
              Font.Assign(UniFont[ftNormal]);
            end;
          pkSmallIcon, pkSmallIcon_AsPreq:
            begin
              ScreenTools.Frame(OffScreen.Canvas, 8 - 1 + x0[I], 2 - 1 + I * 24,
                8 + xSizeSmall + x0[I], 2 + 20 + I * 24, $000000, $000000);
              if HelpLineInfo.Picpix = imPalace then
                BitBltBitmap(OffScreen, 8 + x0[I], 2 + I * 24,
                  xSizeSmall, ySizeSmall, SmallImp,
                  0 * xSizeSmall, 1 * ySizeSmall)
              else
                BitBltBitmap(OffScreen, 8 + x0[I], 2 + I * 24,
                  xSizeSmall, ySizeSmall, SmallImp,
                  HelpLineInfo.Picpix mod 7 * xSizeSmall,
                  (HelpLineInfo.Picpix + SystemIconLines * 7) div 7 *
                  ySizeSmall);
              x0[I] := x0[I] + (8 + 8 + 36);
            end;
          pkBigIcon:
            begin
              FrameImage(OffScreen.Canvas, BigImp, x0[I] + 12, I * 24 - 7, 56,
                40, HelpLineInfo.Picpix mod 7 * xSizeBig,
                HelpLineInfo.Picpix div 7 * ySizeBig);
              x0[I] := 64 + 8 + 8 + x0[I];
            end;
          pkSpecialIcon:
            begin
              case HelpLineInfo.Picpix of
                0:
                  FrameImage(OffScreen.Canvas, HGrSystem2.Data,
                    12 + x0[I], -7 + I * 24, 56, 40, 137, 127);
                1:
                  with NoMap do begin
                    PaintTerrIcon(12 + x0[I], -7 + I * 24,
                      1 + 3 * (xxt * 2 + 1), 1 + yyt);
                    if 2 * yyt < 40 then
                      Sprite(OffScreen, HGrTerrain, 12 + x0[I], -7 + 4 + I * 24,
                        56, 2 * yyt, 1 + 3 * (xxt * 2 + 1) + xxt - 28,
                        1 + yyt + 1 * (yyt * 3 + 1))
                    else
                      Sprite(OffScreen, HGrTerrain, 12 + x0[I],
                        -7 + 4 + I * 24 - 4, 56, 40, 1 + 3 * (xxt * 2 + 1) + xxt
                        - 28, 1 + yyt + 1 * (yyt * 3 + 1) + yyt - 20);
                  end;
                2:
                  with NoMap do begin
                    PaintTerrIcon(12 + x0[I], -7 + I * 24,
                      1 + 7 * (xxt * 2 + 1), 1 + yyt + 4 * (yyt * 3 + 1));
                    if 2 * yyt < 40 then
                      Sprite(OffScreen, HGrTerrain, 12 + x0[I], -7 + 4 + I * 24,
                        56, 32, 1 + 4 * (xxt * 2 + 1) + xxt - 28,
                        1 + yyt + 12 * (yyt * 3 + 1) + yyt - 16)
                    else
                      Sprite(OffScreen, HGrTerrain, 12 + x0[I], -7 + 4 + I * 24,
                        56, 32, 1 + 4 * (xxt * 2 + 1) + xxt - 28,
                        1 + yyt + 12 * (yyt * 3 + 1) + yyt - 16)
                  end;
              end;
              x0[I] := 64 + 8 + 8 + x0[I];
            end;
          pkDomain:
            begin
              ScreenTools.Frame(OffScreen.Canvas, 8 - 1 + x0[I], 2 - 1 + I * 24,
                8 + 36 + x0[I], 2 + 20 + I * 24, $000000, $000000);
              Dump(OffScreen, HGrSystem, 8 + x0[I], 2 + I * 24, 36, 20,
                75 + HelpLineInfo.Picpix * 37, 295);
              x0[I] := x0[I] + (8 + 8 + 36);
            end;
          pkAdvIcon, pkAdvIcon_AsPreq:
            begin
              ScreenTools.Frame(OffScreen.Canvas, 8 - 1 + x0[I], 2 - 1 + I * 24,
                8 + xSizeSmall + x0[I], 2 + ySizeSmall + I * 24,
                $000000, $000000);
              if AdvIcon[HelpLineInfo.Picpix] < 84 then
                BitBltBitmap(OffScreen, 8 + x0[I], 2 + I * 24,
                  xSizeSmall, ySizeSmall, SmallImp,
                  (AdvIcon[HelpLineInfo.Picpix] + SystemIconLines * 7) mod 7 *
                  xSizeSmall, (AdvIcon[HelpLineInfo.Picpix] + SystemIconLines *
                  7) div 7 * ySizeSmall)
              else
                Dump(OffScreen, HGrSystem, 8 + x0[I], 2 + I * 24, 36, 20,
                  1 + (AdvIcon[HelpLineInfo.Picpix] - 84) mod 8 * 37,
                  295 + (AdvIcon[HelpLineInfo.Picpix] - 84) div 8 * 21);
              J := AdvValue[HelpLineInfo.Picpix] div 1000;
              BitBltBitmap(OffScreen, x0[I] + 4, 4 + I * 24, 14, 14,
                HGrSystem.Mask, 127 + J * 15, 85, SRCAND);
              Sprite(OffScreen, HGrSystem, x0[I] + 3, 3 + I * 24, 14, 14,
                127 + J * 15, 85);
              x0[I] := x0[I] + (8 + 8 + 36);
            end;
          pkRightIcon:
            begin
              if Imp[HelpLineInfo.Picpix].Kind <> ikWonder then
                ImpImage(OffScreen.Canvas, InnerWidth - (40 + xSizeBig), I * 24,
                  HelpLineInfo.Picpix, gDespotism)
              else
                WaterSign(InnerWidth - (40 + 2 * xSizeBig), I * 24 - 8,
                  HelpLineInfo.Picpix + 7);
              x0[I] := x0[I] + 8;
            end;
          pkIllu:
            WaterSign(8, I * 24 - 8, HelpLineInfo.Picpix);
          pkBigFeature:
            begin
              cnt := 0;
              for J := nDomains - 1 downto 0 do
                if 1 shl J and Feature[HelpLineInfo.Picpix].Domains <> 0 then
                begin
                  Inc(cnt);
                  Dump(OffScreen, HGrSystem, InnerWidth - 38 - 38 * cnt,
                    I * 24 + 1, 36, 20, 75 + J * 37, 295);
                  ScreenTools.Frame(OffScreen.Canvas, InnerWidth - 39 - 38 * cnt, I * 24,
                    InnerWidth - 2 - 38 * cnt, I * 24 + 21, $000000, $000000);
                end;
              DarkGradient(OffScreen.Canvas, InnerWidth - 38 - 38 * cnt,
                I * 24 + 23, cnt * 38 - 2, 1);
              ofs := InnerWidth - (39 + 7) - 19 * cnt;
              with OffScreen.Canvas do
              begin
                Brush.Color := $C0C0C0;
                FrameRect(Rect(ofs, 1 + 23 + I * 24, ofs + 14,
                  15 + 23 + I * 24));
                Brush.Style := TBrushStyle.bsClear;
                Sprite(OffScreen, HGrSystem, ofs + 2, 3 + 23 + I * 24, 10, 10,
                  66 + HelpLineInfo.Picpix mod 11 * 11,
                  137 + HelpLineInfo.Picpix div 11 * 11);
              end;
              x0[I] := x0[I] + 8;
            end;
          pkTer, pkBigTer:
            with NoMap do begin
              if HelpLineInfo.Format = pkBigTer then
                Y := I * 24 - 3 + yyt
              else
                Y := I * 24 + 13;
              if HelpLineInfo.Picpix >= 3 * 12 then
                srcno := 2 * 9 + 6
              else if HelpLineInfo.Picpix mod 12 = fJungle then
                srcno := 18 * 9
              else if HelpLineInfo.Picpix mod 12 < fJungle then
                srcno := HelpLineInfo.Picpix mod 12
              else
                srcno := 27 + (HelpLineInfo.Picpix mod 12 - 9) * 18;
              if HelpLineInfo.Format = pkTer then
              begin
                ofs := x0[I] + 8;
                x0[I] := 2 * xxt + 8 + ofs;
              end
              else
              begin
                ofs := InnerWidth - (2 * xxt + 38);
                x0[I] := x0[I] + 8;
              end;
              if srcno >= fJungle then
              begin
                Sprite(OffScreen, HGrTerrain, ofs + 4, Y - yyt + 2, xxt * 2 - 8,
                  yyt * 2 - 4, 5 + 2 * (xxt * 2 + 1),
                  3 + yyt + 2 * (yyt * 3 + 1));
                Sprite(OffScreen, HGrTerrain, ofs, Y - 2 * yyt, xxt * 2,
                  yyt * 3 - 2, 1 + srcno mod 9 * (xxt * 2 + 1),
                  1 + srcno div 9 * (yyt * 3 + 1));
              end
              else
                Sprite(OffScreen, HGrTerrain, ofs + 4, Y - yyt + 2, xxt * 2 - 8,
                  yyt * 2 - 4, 5 + srcno mod 9 * (xxt * 2 + 1),
                  3 + yyt + srcno div 9 * (yyt * 3 + 1));
              if HelpLineInfo.Picpix >= 3 * 12 then { rare resource }
                Sprite(OffScreen, HGrTerrain, ofs, Y - 2 * yyt, xxt * 2,
                  yyt * 3, 1 + 8 * (xxt * 2 + 1),
                  1 + (HelpLineInfo.Picpix - 2 * 12) * (yyt * 3 + 1))
              else if HelpLineInfo.Picpix >= 12 then { special tile }
              begin
                if HelpLineInfo.Picpix mod 12 = fJungle then
                  srcno := 17 * 9 + 8
                else if HelpLineInfo.Picpix mod 12 < fJungle then
                  srcno := HelpLineInfo.Picpix mod 12
                else
                  srcno := 18 + 8 + (HelpLineInfo.Picpix mod 12 - 9) * 18;
                srcno := srcno + HelpLineInfo.Picpix div 12 * 9;
                Sprite(OffScreen, HGrTerrain, ofs, Y - 2 * yyt, xxt * 2,
                  yyt * 3, 1 + srcno mod 9 * (xxt * 2 + 1),
                  1 + srcno div 9 * (yyt * 3 + 1));
              end;
            end;
          pkTerImp:
            with NoMap do begin
              ofs := 8;
              if HelpLineInfo.Picpix = 5 then
              begin // display mine on hills
                Sprite(OffScreen, HGrTerrain, ofs + 4, I * 24 + 13 - yyt,
                  xxt * 2 - 8, yyt * 2 - 4, 5 + 2 * (xxt * 2 + 1),
                  3 + yyt + 2 * (yyt * 3 + 1));
                srcno := 45;
              end
              else
                srcno := fPrairie; // display on prairie
              Sprite(OffScreen, HGrTerrain, ofs + 4, I * 24 + 13 - yyt,
                xxt * 2 - 8, yyt * 2 - 4, 5 + srcno mod 9 * (xxt * 2 + 1),
                3 + yyt + srcno div 9 * (yyt * 3 + 1));
              if HelpLineInfo.Picpix = 12 then { river }
                Sprite(OffScreen, HGrTerrain, ofs, I * 24 + 11 - yyt, xxt * 2,
                  yyt * 2, 1 + 5 * (xxt * 2 + 1), 1 + yyt + 13 * (yyt * 3 + 1))
              else if HelpLineInfo.Picpix >= 3 then { improvement 2 }
              begin
                if HelpLineInfo.Picpix = 6 then
                  Sprite(OffScreen, HGrTerrain, ofs, I * 24 + 11 - 2 * yyt,
                    xxt * 2, yyt * 3, 1 + 7 * (xxt * 2 + 1),
                    1 + 12 * (yyt * 3 + 1));
                Sprite(OffScreen, HGrTerrain, ofs, I * 24 + 11 - 2 * yyt,
                  xxt * 2, yyt * 3, 1 + (HelpLineInfo.Picpix - 3) *
                  (xxt * 2 + 1), 1 + 12 * (yyt * 3 + 1))
              end
              else { improvement 1 }
              begin
                Sprite(OffScreen, HGrTerrain, ofs, I * 24 + 11 - 2 * yyt,
                  xxt * 2, yyt * 3, 1 + 2 * (xxt * 2 + 1),
                  1 + (9 + HelpLineInfo.Picpix) * (yyt * 3 + 1));
                Sprite(OffScreen, HGrTerrain, ofs, I * 24 + 11 - 2 * yyt,
                  xxt * 2, yyt * 3, 1 + 5 * (xxt * 2 + 1),
                  1 + (9 + HelpLineInfo.Picpix) * (yyt * 3 + 1))
              end;
              x0[I] := x0[I] + 8;
            end;
          pkModel:
            begin
              FrameImage(OffScreen.Canvas, BigImp, x0[I] + 12, I * 24 - 7,
                56, 40, 0, 0);
              Sprite(OffScreen, HGrStdUnits, x0[I] + 8, I * 24 - 11, 64, 44,
                1 + HelpLineInfo.Picpix mod 10 * 65,
                1 + HelpLineInfo.Picpix div 10 * 49);
              x0[I] := 64 + 8 + 8 + x0[I];
            end;
          pkFeature:
            begin
              DarkGradient(OffScreen.Canvas, x0[I] + 8 - 1,
                7 + I * 24 - 3, 16, 1);
              ScreenTools.Frame(OffScreen.Canvas, x0[I] + 8, 7 + I * 24 - 2, x0[I] + 8 + 13,
                7 + I * 24 - 2 + 13, $C0C0C0, $C0C0C0);
              Sprite(OffScreen, HGrSystem, x0[I] + 8 + 2, 7 + I * 24, 10, 10,
                66 + HelpLineInfo.Picpix mod 11 * 11,
                137 + HelpLineInfo.Picpix div 11 * 11);
              x0[I] := x0[I] + 8 + 8 + 2 + 13;
            end;
          pkExp:
            begin
              ScreenTools.Frame(OffScreen.Canvas, 20 - 1, 8 - 4 + I * 24, 20 + 12,
                8 + 11 + I * 24, $000000, $000000);
              Dump(OffScreen, HGrSystem, 20, 8 - 3 + I * 24, 12, 14,
                121 + HelpLineInfo.Picpix * 13, 28);
              x0[I] := 20 + 8 + 11;
            end;
          pkAITStat:
            begin
              Sprite(OffScreen, HGrSystem, 20, 6 + I * 24, 14, 14,
                1 + HelpLineInfo.Picpix * 15, 316);
              x0[I] := 20 + 8 + 11;
            end;
          pkGov:
            begin
              ScreenTools.Frame(OffScreen.Canvas, 8 - 1 + x0[I], 2 - 1 + I * 24,
                8 + xSizeSmall + x0[I], 2 + 20 + I * 24, $000000, $000000);
              BitBltBitmap(OffScreen, 8 + x0[I], 2 + I * 24, xSizeSmall,
                ySizeSmall, SmallImp, (HelpLineInfo.Picpix - 1) *
                xSizeSmall, ySizeSmall);
              x0[I] := x0[I] + (8 + 8 + 36);
            end;
          pkDot:
            begin
              Sprite(OffScreen, HGrSystem, x0[I] + 18, 9 + I * 24, 8,
                8, 81, 16);
              x0[I] := 20 + 8 + 4;
            end;
          pkNormal_Dot:
            x0[I] := 20 + 8 + 4;
          pkNormal_64:
            x0[I] := 64 + 8 + 8;
        else
          x0[I] := x0[I] + 8;
        end;
        Self.Line(OffScreen.Canvas, I, False);
      end;
  end;
  MarkUsedOffscreen(InnerWidth, InnerHeight + 13 + 48);
end;

procedure THelpDlg.DoOnResize;
begin
  inherited;
  SearchBtn.Left := Width - 67;
  CloseBtn.Left := Width - 38;
end;

procedure THelpDlg.ScrollBarUpdate(Sender: TObject);
begin
  Sel := -1;
  SmartUpdateContent(True);
end;

procedure THelpDlg.Prepare(sbPos: Integer = 0);
var
  I, J, Special, Domain, Headline, TerrType, TerrSubType: Integer;
  S: string;
  ps: PChar;
  List: THyperText;
  CheckSeeAlso: Boolean;

  procedure AddAdvance(I: Integer);
  begin
    MainText.AddLine(Phrases['ADVANCES', I], pkAdvIcon, I,
      hkAdv, I, True);
  end;

  procedure AddPreqAdv(I: Integer);
  begin
    MainText.AddLine(Phrases['ADVANCES', I], pkAdvIcon_AsPreq, I,
      hkAdv, I, True);
  end;

  procedure AddImprovement(I: Integer);
  begin
    MainText.AddLine(Phrases['IMPROVEMENTS', I], pkSmallIcon, I,
      hkImp, I, True);
  end;

  procedure AddPreqImp(I: Integer);
  begin
    MainText.AddLine(Phrases['IMPROVEMENTS', I], pkSmallIcon_AsPreq, I,
      hkImp, I, True);
  end;

  procedure AddTerrain(I: Integer);
  begin
    if MainText.Count > 1 then
    begin
      MainText.LineFeed;
    end;
    MainText.AddLine(Phrases['TERRAIN', I], pkTer, I, hkTer, I);
  end;

  procedure AddFeature(I: Integer);
  begin
    MainText.AddLine(Phrases['FEATURES', I], pkFeature, I,
      hkFeature, I, True);
  end;

  procedure AddModel(I: Integer);
  var
    pix: Integer;
    Name: string;
  begin
    if MainText.Count > 1 then
      MainText.LineFeed;
    FindStdModelPicture(SpecialModelPictureCode[I], pix, Name);
    MainText.AddLine(Name, pkModel, pix, hkModel, I, True);
  end;

  procedure AddStandardBlock(Item: string);
  var
    I: Integer;
  begin
    with MainText do
    begin
      if Item = 'LOGO' then
      begin
        AddLine('', pkLogo);
        LineFeed;
      end
      else if Item = 'TECHFORMULA' then
      begin
        I := Difficulty;
        if I = 0 then
          I := 2;
        AddLine(Format(HelpText['TECHFORMULA'], [TechFormula_M[I],
          TechFormula_D[I]]));
      end
      else if Item = 'EXPERIENCE' then
        for I := 0 to nExp - 1 do
          AddLine(Phrases['EXPERIENCE', I], pkExp, I)
      else if Item = 'MODERN' then
        for I := 1 to 3 do
        begin
          LineFeed;
          AddLine(Phrases['TERRAIN', 3 * 12 + I], pkTer, 3 * 12 + I);
        end
      else if Item = 'SAVED' then
        AddLine(DataDir + 'Saved', pkNormal)
      else if Item = 'AITSTAT' then
        for I := 0 to 3 do
          AddLine(Phrases2['AITSTAT', I], pkAITStat, I);
    end;
  end;

  procedure DecodeItem(S: string; var Category: TLinkCategory; var Index: Integer);
  var
    I: Integer;
  begin
    if (Length(S) > 0) and (S[1] = ':') then begin
      Category := hkMisc;
      Index := 0;
      for I := 3 to Length(S) do
        Index := Index * 10 + Ord(S[I]) - 48;
      case S[2] of
        'A': Category := hkAdv;
        'B': Category := hkImp;
        'T': Category := hkTer;
        'F': Category := hkFeature;
        'E': Category := hkInternet;
        'S': Category := hkModel;
        'C': Index := Integer(miscCredits);
        'J': Index := Integer(miscJobList);
        'G': Index := Integer(miscGovList);
      end;
      if (Category <> hkMisc) and (Index = 0) then
        Index := 200;
    end else begin
      Category := hkText;
      Index := HelpText.GetHandle(Copy(S, 1, 255));
    end;
  end;

  procedure AddTextual(S: string);
  var
    I: Integer;
    P: Integer;
    L: Integer;
    ofs: Integer;
    CurrentFormat: TTextFormat;
    FollowFormat: TTextFormat;
    Picpix: Integer;
    LinkCategory: TLinkCategory;
    CrossLink: Boolean;
    LinkIndex: Integer;
    RightMargin: Integer;
    Name: string;
    Text: string;
  begin
    CrossLink := False;
    RightMargin := InnerWidth - 16 - GetSystemMetrics(SM_CXVSCROLL);
    FollowFormat := pkNormal;
    while S <> '' do
    begin
      Picpix := 0;
      LinkCategory := hkNoLink;
      LinkIndex := 0;
      if S[1] = '$' then
      begin // Window caption
        P := 1;
        repeat
          Inc(P);
        until (P > Length(S)) or (S[P] = '\');
        Caption := Copy(S, 2, P - 2);
        Delete(S, 1, P);
      end
      else if S[1] = '&' then
      begin // Standard block
        P := 1;
        repeat
          Inc(P);
        until (P > Length(S)) or (S[P] = '\');
        AddStandardBlock(Copy(S, 2, P - 2));
        Delete(S, 1, P);
      end
      else if S[1] = '@' then
      begin // Image
        if (Length(S) >= 2) and (S[2] = '@') then
        begin // Generate from icon
          Picpix := 0;
          P := 3;
          while (P <= Length(S)) and (S[P] <> '\') do
          begin
            Picpix := Picpix * 10 + Ord(S[P]) - 48;
            Inc(P);
          end;
          if (Picpix < 0) or (Picpix >= nImp) then
            Picpix := 0;
          MainText.AddLine('', pkIllu, Picpix);
          MainText.LineFeed;
          MainText.LineFeed;
        end
        else
        begin // external image
          P := 1;
          repeat
            Inc(P);
          until (P > Length(S)) or (S[P] = '\');
          if LoadGraphicFile(ExtPic, LocalizedFilePath('Help' +
            DirectorySeparator + Copy(S, 2, P - 2) + PngExt)) then
          begin
            MainText.AddLine('', pkExternal);
            for I := 0 to (ExtPic.Height - 12) div 24 do
              MainText.LineFeed;
          end;
        end;
        Delete(S, 1, P);
      end
      else
      begin
        case S[1] of
          ':', ';':
            begin // link
              P := 1;
              repeat
                Inc(P);
              until (P > Length(S)) or (S[P] = '\') or (S[P] = ' ');
              DecodeItem(Copy(S, 2, P - 2), LinkCategory, LinkIndex);
              CurrentFormat := pkNormal;
              if (LinkCategory <> hkText) and (LinkIndex < 200) then
              // show icon
                case LinkCategory of
                  hkAdv:
                    begin
                      CurrentFormat := pkAdvIcon;
                      Picpix := LinkIndex;
                    end;
                  hkImp:
                    begin
                      CurrentFormat := pkSmallIcon;
                      Picpix := LinkIndex;
                    end;
                  hkTer:
                    begin
                      CurrentFormat := pkTer;
                      Picpix := LinkIndex;
                    end;
                  hkFeature:
                    begin
                      CurrentFormat := pkFeature;
                      Picpix := LinkIndex;
                    end;
                  hkModel:
                    begin
                      CurrentFormat := pkModel;
                      FindStdModelPicture(SpecialModelPictureCode[LinkIndex],
                        Picpix, Name);
                    end;
                end;
              if S[1] = ':' then
                CrossLink := True;
              if (P > Length(S)) or (S[P] = ' ') then
                Delete(S, 1, P)
              else
                Delete(S, 1, P - 1);
            end;
          '!': // highlighted
            if (Length(S) >= 2) and (S[2] = '!') then
            begin
              if MainText.Count > 1 then
                MainText.LineFeed;
              FollowFormat := pkCaption;
              CurrentFormat := pkCaption;
              Delete(S, 1, 2);
            end
            else
            begin
              FollowFormat := pkSection;
              CurrentFormat := pkSection;
              Delete(S, 1, 1);
            end;
          '-':
            begin // list
              FollowFormat := pkNormal_Dot;
              CurrentFormat := pkDot;
              Delete(S, 1, 1);
            end;
        else
          CurrentFormat := FollowFormat;
        end;
        if FollowFormat = pkNormal_Dot then
          ofs := 20 + 4 + 8
        else
          ofs := 8;
        P := 0;
        repeat
          repeat
            Inc(P);
          until (P > Length(S)) or (S[P] = ' ') or (S[P] = '\');
          if (BiColorTextWidth(OffScreen.Canvas, Copy(S, 1, P - 1)) <=
            RightMargin - ofs) then
            L := P - 1
          else
            Break;
        until (P >= Length(S)) or (S[L + 1] = '\');
        Text := Copy(S, 1, L);
        if LinkCategory = hkInternet then begin
          if LinkIndex = 1 then Text := AITemplateManual
          else if LinkIndex = 2 then Text := CevoHomepageShort
          else if LinkIndex = 3 then Text := CevoContactShort;
        end;
        MainText.AddLine(Text, CurrentFormat, Picpix, LinkCategory,
          LinkIndex, CrossLink);
        if (L < Length(S)) and (S[L + 1] = '\') then
          FollowFormat := pkNormal;
        Delete(S, 1, L + 1);
      end;
    end;
  end;

  procedure AddItem(Item: string);
  begin
    AddTextual(HelpText[Item]);
  end;

  procedure AddModelText(I: Integer);
  var
    pix: Integer;
    S: string;
  begin
    with MainText do begin
      if Count > 1 then begin
        LineFeed;
        LineFeed;
      end;
      FindStdModelPicture(SpecialModelPictureCode[I], pix, S);
      AddLine(S, pkSection);
      AddLine(Format(HelpText['STRENGTH'], [SpecialModel[I].Attack,
        SpecialModel[I].Defense]), pkNormal_64);
      AddLine(Format(HelpText['SPEED'],
        [MovementToString(SpecialModel[I].Speed)]), pkModel, pix);
      if Difficulty = 0 then
        AddLine(Format(HelpText['BUILDCOST'], [SpecialModel[I].Cost]),
          pkNormal_64)
      else
        AddLine(Format(HelpText['BUILDCOST'],
          [SpecialModel[I].Cost * BuildCostMod[Difficulty] div 12]),
          pkNormal_64);
      S := HelpText.LookupByHandle(hSPECIALMODEL, I);
      if (S <> '') and (S <> '*') then
        AddTextual(S);
      if SpecialModelPreq[I] >= 0 then
        AddPreqAdv(SpecialModelPreq[I])
      else if SpecialModelPreq[I] = preLighthouse then
        AddPreqImp(woLighthouse)
      else if SpecialModelPreq[I] = preBuilder then
        AddPreqImp(woPyramids)
      else if SpecialModelPreq[I] = preLeo then
        AddPreqImp(woLeo);
      if SpecialModelPreq[I] <> preNone then
        MainText[Count - 1] := Format(HelpText['REQUIRED'],
          [MainText[Count - 1]]);
    end;
  end;

  procedure AddJobList;
  var
    I, JobCost: Integer;
  begin
    with MainText do begin
      for I := 0 to Length(JobHelp) - 1 do begin
        if I > 0 then begin
          LineFeed;
          LineFeed;
        end;
        AddLine(Phrases['JOBRESULT', JobHelp[I]], pkSection);
        AddLine;
        AddLine('', pkTerImp, I);
        AddLine;
        AddTextual(HelpText.LookupByHandle(hJOBHELP, I));
        JobCost := -1;
        case JobHelp[I] of
          jCanal: JobCost := CanalWork;
          jFort: JobCost := FortWork;
          jBase: JobCost := BaseWork;
        end;
        if JobCost >= 0 then
          AddTextual(Format(HelpText['JOBCOST'],
            [MovementToString(JobCost)]))
        else
          AddTextual(HelpText['JOBCOSTVAR']);
        if JobPreq[JobHelp[I]] <> preNone then begin
          AddPreqAdv(JobPreq[JobHelp[I]]);
          MainText[Count - 1] := Format(HelpText['REQUIRED'],
            [MainText[Count - 1]]);
        end;
      end;
    end;
  end;

  procedure AddGraphicCredits;
  var
    I: Integer;
    S: string;
    SearchRec: TSearchRec;
    List, Plus: TStringList;
  begin
    List := TStringList.Create;
    Plus := TStringList.Create;
    if FindFirst(GetGraphicsDir + DirectorySeparator + '*.credits.txt', $27, SearchRec) = 0 then
      repeat
        Plus.LoadFromFile(GetGraphicsDir + DirectorySeparator + SearchRec.Name);
        List.AddStrings(Plus);
      until FindNext(SearchRec) <> 0;
    FindClose(SearchRec);
    FreeAndNil(Plus);

    List.Sort;
    I := 1;
    while I < List.Count do
      if List[I] = List[I - 1] then
        List.Delete(I)
      else
        Inc(I);

    for I := 0 to List.Count - 1 do begin
      S := List[I];
      while BiColorTextWidth(OffScreen.Canvas, S) > InnerWidth - 16 -
        GetSystemMetrics(SM_CXVSCROLL) do
        Delete(S, Length(S), 1);
      MainText.AddLine(S);
    end;
    FreeAndNil(List);
  end;

  procedure AddSoundCredits;
  var
    I: Integer;
    S: string;
    List: TStringList;
  begin
    List := TStringList.Create;
    List.LoadFromFile(GetSoundsDir + DirectorySeparator + 'sound.credits.txt');
    for I := 0 to List.Count - 1 do begin
      S := List[I];
      while BiColorTextWidth(OffScreen.Canvas, S) > InnerWidth - 16 -
        GetSystemMetrics(SM_CXVSCROLL) do
        Delete(S, Length(S), 1);
      MainText.AddLine(S);
    end;
    FreeAndNil(List);
  end;

  procedure NextSection(Item: string);
  begin
    if MainText.Count > 1 then
      if MainText.Count = Headline + 1 then
        MainText.Delete(Headline)
      else
        MainText.LineFeed;
    MainText.AddLine(HelpText[Item], pkSection);
    Headline := MainText.Count - 1;
  end;

begin { Prepare }
  with MainText do begin
    OffScreen.Canvas.Font.Assign(UniFont[ftNormal]);
    CheckSeeAlso := False;
    Clear;
    Headline := -1;
    if (No >= 200) or not (Kind in [hkAdv, hkImp, hkTer, hkFeature]) then
      LineFeed;
    case Kind of
      hkText:
        AddTextual(HelpText.LookupByHandle(No));
      hkMisc:
        begin
          case No of
            Integer(miscMain):
              begin
                Caption := HelpText['HELPTITLE_MAIN'];
                AddLine(HelpText['HELPTITLE_QUICKSTART'], pkSpecialIcon,
                  0, { pkBigIcon,22, } hkText, HelpText.GetHandle('QUICK'));
                LineFeed;
                AddLine(HelpText['HELPTITLE_CONCEPTS'], pkBigIcon, 6,
                  hkText, HelpText.Gethandle('CONCEPTS'));
                LineFeed;
                AddLine(HelpText['HELPTITLE_TERLIST'], pkSpecialIcon, 1,
                  hkTer, 200);
                LineFeed;
                AddLine(HelpText['HELPTITLE_JOBLIST'], pkSpecialIcon, 2,
                  hkMisc, Integer(miscJobList));
                LineFeed;
                AddLine(HelpText['HELPTITLE_TECHLIST'], pkBigIcon, 39,
                  hkAdv, 200);
                LineFeed;
                FindStdModelPicture(SpecialModelPictureCode[6], I, S);
                AddLine(HelpText['HELPTITLE_MODELLIST'], pkModel, I,
                  hkModel, 0);
                LineFeed;
                AddLine(HelpText['HELPTITLE_FEATURELIST'], pkBigIcon, 28,
                  hkFeature, 200);
                LineFeed;
                AddLine(HelpText['HELPTITLE_IMPLIST'], pkBigIcon,
                  7 * SystemIconLines + imCourt, hkImp, 200);
                LineFeed;
                AddLine(HelpText['HELPTITLE_UNIQUELIST'], pkBigIcon,
                  7 * SystemIconLines + imStockEx, hkImp, 201);
                LineFeed;
                AddLine(HelpText['HELPTITLE_WONDERLIST'], pkBigIcon,
                  7 * SystemIconLines, hkImp, 202);
                LineFeed;
                AddLine(HelpText['HELPTITLE_GOVLIST'], pkBigIcon,
                  gDemocracy + 6, hkMisc, Integer(miscGovList));
                LineFeed;
                AddLine(HelpText['HELPTITLE_KEYS'], pkBigIcon, 2, hkText,
                  HelpText.Gethandle('HOTKEYS'));
                LineFeed;
                AddLine(HelpText['HELPTITLE_ABOUT'], pkBigIcon, 1,
                  hkText, HelpText.Gethandle('ABOUT'));
                LineFeed;
                AddLine(HelpText['HELPTITLE_CREDITS'], pkBigIcon, 22,
                  hkMisc, Integer(miscCredits));
              end;
            Integer(miscCredits):
              begin
                AddItem('CREDITS');
                LineFeed;
                AddGraphicCredits;
                NextSection('CRED_CAPSOUND');
                AddSoundCredits;
                NextSection('CRED_CAPAI');
                Server(sGetAICredits, 0, 0, ps);
                AddTextual(ps);
                NextSection('CRED_CAPLANG');
                AddItem('AUTHOR');
              end;
            Integer(miscJobList):
              begin
                Caption := HelpText['HELPTITLE_JOBLIST'];
                AddJobList;
                LineFeed;
                AddItem('TERIMPEXCLUDE');
                LineFeed;
                AddItem('TERIMPCITY');
              end;
            Integer(miscGovList):
              begin
                Caption := HelpText['HELPTITLE_GOVLIST'];
                for I := 1 to nGov do
                begin
                  AddLine(Phrases['GOVERNMENT', I mod nGov], pkSection);
                  LineFeed;
                  if I = nGov then
                    AddLine('', pkBigIcon, 7 * SystemIconLines + imPalace)
                  else
                    AddLine('', pkBigIcon, I + 6);
                  LineFeed;
                  AddTextual(HelpText.LookupByHandle(hGOVHELP, I mod nGov));
                  if I mod nGov >= 2 then
                  begin
                    AddPreqAdv(GovPreq[I mod nGov]);
                    MainText[Count - 1] := Format(HelpText['REQUIRED'],
                      [MainText[Count - 1]]);
                  end;
                  if I < nGov then
                  begin
                    LineFeed;
                    LineFeed;
                  end;
                end;
              end;
            Integer(miscSearchResult):
              begin
                Caption := HelpText['HELPTITLE_SEARCHRESULTS'];
                AddTextual(Format(HelpText['MATCHES'], [SearchContent]));
                MainText.AppendList(SearchResult);
              end;
          end; // case No
        end;

      hkAdv:
        if No = 200 then
        begin // complete advance list
          Caption := HelpText['HELPTITLE_TECHLIST'];
          List := THyperText.Create;
          List.OwnsObjects := True;
          for J := 0 to 3 do
          begin
            if J > 0 then
            begin
              LineFeed;
              LineFeed;
            end;
            AddLine(HelpText['TECHAGE', J], pkSection);
            if J = 1 then
              AddLine(Phrases['ADVANCES', adScience] + ' ' +
                HelpText['BASETECH'], pkAdvIcon, adScience, hkAdv,
                adScience);
            if J = 2 then
              AddLine(Phrases['ADVANCES', adMassProduction] + ' ' +
                HelpText['BASETECH'], pkAdvIcon, adMassProduction, hkAdv,
                adMassProduction);
            List.Clear;
            for I := 0 to nAdv - 1 do
              if (I <> adScience) and (I <> adMassProduction) and
                (AdvValue[I] div 1000 = J) then
                List.AddLine(Phrases['ADVANCES', I], pkAdvIcon, I,
                  hkAdv, I);
            List.Sort;
            AppendList(List);
          end;
          FreeAndNil(List);
        end
        else // single advance
        begin
          Caption := Phrases['ADVANCES', No];
          LineFeed;
          AddLine(Phrases['ADVANCES', No], pkCaption);
          if No in FutureTech then
          begin
            AddLine(HelpText['HELPSPEC_FUTURE']);
            LineFeed;
            if No = futResearchTechnology then
              AddItem('FUTURETECHHELP100')
            else
              AddItem('FUTURETECHHELP25');
          end
          else
            AddLine(HelpText['HELPSPEC_ADV']);
          if AdvPreq[No, 2] <> preNone then
            NextSection('PREREQALT')
          else
            NextSection('PREREQ');
          for I := 0 to 2 do
            if AdvPreq[No, I] <> preNone then
              AddPreqAdv(AdvPreq[No, I]);
          NextSection('GOVALLOW');
          for I := 2 to nGov - 1 do
            if GovPreq[I] = No then
              AddLine(Phrases['GOVERNMENT', I], pkGov, I,
                hkMisc, Integer(miscGovList), True);
          NextSection('BUILDALLOW');
          for I := 0 to nWonder - 1 do
            if Imp[I].Preq = No then
              AddImprovement(I);
          for I := nWonder to nImp - 1 do
            if (Imp[I].Preq = No) and (Imp[I].Kind <> ikCommon) then
              AddImprovement(I);
          for I := nWonder to nImp - 1 do
            if (Imp[I].Preq = No) and (Imp[I].Kind = ikCommon) then
              AddImprovement(I);
          NextSection('MODELALLOW');
          for I := 0 to nSpecialModel - 1 do
            if SpecialModelPreq[I] = No then
              AddModel(I);
          NextSection('FEATALLOW');
          for I := 0 to nFeature - 1 do
            if Feature[I].Preq = No then
              AddFeature(I);
          NextSection('FOLLOWADV');
          for I := 0 to nAdv - 1 do
            if (AdvPreq[I, 0] = No) or (AdvPreq[I, 1] = No) or
              (AdvPreq[I, 2] = No) then
              AddAdvance(I);
          NextSection('UPGRADEALLOW');
          for Domain := 0 to nDomains - 1 do
            for I := 1 to nUpgrade - 1 do
              if Upgrade[Domain, I].Preq = No then
              begin
                if Upgrade[Domain, I].Strength > 0 then
                  AddLine(Format(HelpText['STRENGTHUP'],
                    [Phrases['DOMAIN', Domain], Upgrade[Domain,
                    I].Strength]), pkDomain, Domain);
                if Upgrade[Domain, I].Trans > 0 then
                  AddLine(Format(HelpText['TRANSUP'],
                    [Phrases['DOMAIN', Domain], Upgrade[Domain, I].Trans]
                    ), pkDomain, Domain);
                if No in FutureTech then
                  AddLine(Format(HelpText['COSTUP'],
                    [Upgrade[Domain, I].Cost]), pkNormal_Dot)
                else
                  AddLine(Format(HelpText['COSTMIN'],
                    [Upgrade[Domain, I].Cost]), pkNormal_Dot)
              end;
          NextSection('EXPIRATION');
          for I := 0 to nWonder - 1 do
            if (Imp[I].Preq <> preNA) and (Imp[I].Expiration = No) then
              AddImprovement(I);
          NextSection('ADVEFFECT');
          S := HelpText.LookupByHandle(hADVHELP, No);
          if S <> '*' then
            AddTextual(S);
          NextSection('SEEALSO');
          CheckSeeAlso := True;
        end;

      hkImp:
        if No = 200 then
        begin // complete city improvement list
          Caption := HelpText['HELPTITLE_IMPLIST'];
          // AddLine(HelpText['HELPTITLE_IMPLIST'),pkSection);
          List := THyperText.Create;
          List.OwnsObjects := True;
          for I := nWonder to nImp - 1 do
            if (I <> imTrGoods) and (Imp[I].Preq <> preNA) and
              (Imp[I].Kind = ikCommon) then
              List.AddLine(Phrases['IMPROVEMENTS', I], pkSmallIcon,
                I, hkImp, I);
          List.Sort;
          AppendList(List);
          FreeAndNil(List);
        end
        else if No = 201 then
        begin // complete nat. project list
          Caption := HelpText['HELPTITLE_UNIQUELIST'];
          // AddLine(HelpText['HELPTITLE_UNIQUELIST'), pkSection);
          for I := nWonder to nImp - 1 do
            if (Imp[I].Preq <> preNA) and
              ((Imp[I].Kind = ikNatLocal) or (Imp[I].Kind = ikNatGlobal)) then
              AddLine(Phrases['IMPROVEMENTS', I], pkSmallIcon, I,
                hkImp, I);
          { LineFeed;
            LineFeed;
            AddLine(HelpText['HELPTITLE_SHIPPARTLIST'), pkSection);
            for I := nWonder to nImp - 1 do
            if (Imp[I].Preq <> preNA) and (Imp[I].Kind = ikShipPart) then
            AddLine(Phrases['IMPROVEMENTS', I), pkSmallIcon, I, hkImp, I); }
        end
        else if No = 202 then
        begin // complete wonder list
          Caption := HelpText['HELPTITLE_WONDERLIST'];
          // AddLine(HelpText['HELPTITLE_WONDERLIST'),pkSection);
          for I := 0 to nWonder - 1 do
            if Imp[I].Preq <> preNA then
              AddLine(Phrases['IMPROVEMENTS', I], pkSmallIcon, I,
                hkImp, I);
        end
        else
        begin // single building
          Caption := Phrases['IMPROVEMENTS', No];
          LineFeed;
          AddLine(Phrases['IMPROVEMENTS', No], pkRightIcon, No);
          case Imp[No].Kind of
            ikWonder: AddLine(HelpText['HELPSPEC_WONDER']);
            ikCommon: AddLine(HelpText['HELPSPEC_IMP']);
            ikShipPart: AddLine(HelpText['HELPSPEC_SHIPPART']);
          else
            AddLine(HelpText['HELPSPEC_NAT'])
          end;
          if Imp[No].Kind <> ikShipPart then begin
            NextSection('EFFECT');
            AddTextual(HelpText.LookupByHandle(hIMPHELP, No));
          end;
          if No = woSun then begin
            AddFeature(mcFirst);
            AddFeature(mcWill);
            AddFeature(mcAcademy);
          end;
          if (No < nWonder) and not Phrases2FallenBackToEnglish then
          begin
            LineFeed;
            if Imp[No].Expiration >= 0 then
              AddTextual(Phrases2['HELP_WONDERMORALE1'])
            else
              AddTextual(Phrases2['HELP_WONDERMORALE2']);
          end;
          if Imp[No].Preq <> preNone then
          begin
            NextSection('PREREQ');
            AddPreqAdv(Imp[No].Preq);
          end;
          NextSection('COSTS');
          if Difficulty = 0 then
            S := Format(HelpText['BUILDCOST'], [Imp[No].Cost])
          else
            S := Format(HelpText['BUILDCOST'],
              [Imp[No].Cost * BuildCostMod[Difficulty] div 12]);
          AddLine(S);
          if Imp[No].Maint > 0 then
            AddLine(Format(HelpText['MAINTCOST'], [Imp[No].Maint]));
          J := 0;
          for I := 0 to nImpReplacement - 1 do
            if ImpReplacement[I].NewImp = No then
            begin
              if J = 0 then
              begin
                NextSection('REPLACE');
                AddItem('REPLACETEXT');
                J := 1;
              end;
              AddImprovement(ImpReplacement[I].OldImp);
            end;
          if Imp[No].Kind = ikShipPart then
          begin
            LineFeed;
            if No = imShipComp then
              I := 1
            else if No = imShipPow then
              I := 2
            else { if No=imShipHab then }
              I := 3;
            AddLine(Format(HelpText['RAREREQUIRED'],
              [Phrases['TERRAIN', 3 * 12 + I]]), pkTer, 3 * 12 + I);
          end;
          if (No < nWonder) and (Imp[No].Expiration >= 0) then
          begin
            NextSection('EXPIRATION');
            S := Format(HelpText['EXPWITH'],
              [Phrases['ADVANCES', Imp[No].Expiration]]);
            if No = woPyramids then
              S := S + ' ' + HelpText['EXPSLAVE'];
            AddTextual(S);
          end;
          NextSection('SEEALSO');
          if (No < nWonder) and (Imp[No].Expiration >= 0) then
            AddImprovement(woEiffel);
          for I := 0 to nImpReplacement - 1 do
            if ImpReplacement[I].OldImp = No then
              AddImprovement(ImpReplacement[I].NewImp);
          if No = imSupermarket then
            AddLine(HelpText['HELPTITLE_JOBLIST'], pkNormal, 0,
              hkMisc, Integer(miscJobList), True);
          CheckSeeAlso := True;
        end;

      hkTer:
        if No = 200 then
        begin // complete terrain type list
          Caption := HelpText['HELPTITLE_TERLIST'];
          // AddLine(HelpText['HELPTITLE_TERLIST'],pkSection);
          for I := 0 to Length(TerrainHelp) - 1 do
            AddTerrain(TerrainHelp[I]);
        end
        else
        begin // sigle terrain type
          TerrType := No mod 12;
          if TerrType = fJungle then
            TerrType := fForest;
          TerrSubType := No div 12;
          if No = 3 * 12 then
          begin
            TerrType := fDesert;
            TerrSubType := 0;
          end;
          with Terrain[TerrType] do
          begin
            Caption := Phrases['TERRAIN', No];
            LineFeed;
            AddLine(Phrases['TERRAIN', No], pkBigTer, No);
            AddLine(HelpText['HELPSPEC_TER']);
            LineFeed;
            if (ProdRes[TerrSubType] > 0) or (MineEff > 0) then
              AddLine(Format(HelpText['RESPROD'],
                [ProdRes[TerrSubType]]));
            if (No < 3 * 12) and (MineEff > 0) then
              MainText[Count - 1] := MainText[Count - 1] + ' ' +
                Format(HelpText['MOREMINE'], [MineEff]);
            if (FoodRes[TerrSubType] > 0) or (IrrEff > 0) then
              AddLine(Format(HelpText['RESFOOD'],
                [FoodRes[TerrSubType]]));
            if (No < 3 * 12) and (IrrEff > 0) then
              MainText[Count - 1] := MainText[Count - 1] + ' ' +
                Format(HelpText['MOREIRR'], [IrrEff]);
            if TradeRes[TerrSubType] > 0 then
              AddLine(Format(HelpText['RESTRADE'],
                [TradeRes[TerrSubType]]));
            if Defense > 4 then
              AddLine(Format(HelpText['DEFBONUS'],
                [(Defense - 4) * 25]));
            if (TerrType >= fGrass) and (TerrType <> fMountains) then
              if MoveCost = 2 then
                AddLine(HelpText['MOVEHEAVY'])
              else
                AddLine(HelpText['MOVEPLAIN']);
            if No = 3 * 12 then
            begin
              LineFeed;
              AddTextual(HelpText['DEADLANDS']);
            end;
            if (TerrType = fDesert) and (No <> fDesert + 12) then
            begin
              LineFeed;
              AddTextual(Format(HelpText['HOSTILE'], [DesertThurst]));
            end;
            if TerrType = fArctic then
            begin
              LineFeed;
              AddTextual(Format(HelpText['HOSTILE'], [ArcticThurst]));
            end;
            if (No < 3 * 12) and (TransTerrain >= 0) then
            begin
              LineFeed;
              I := TransTerrain;
              if (TerrType <> fGrass) and (I <> fGrass) then
                I := I + TerrSubType * 12;
              // trafo to same Special resource group
              AddLine(Format(HelpText['TRAFO'],
                [Phrases['TERRAIN', I]]), pkTer, I,
                hkTer, I, True);
              if No = fSwamp + 12 then
              begin
                LineFeed;
                AddLine(Format(HelpText['TRAFO'],
                  [Phrases['TERRAIN', TransTerrain + 24]]), pkTer,
                  TransTerrain + 24, hkTer, TransTerrain + 24, True);
              end
              else if I = fGrass then
              begin
                LineFeed;
                AddLine(Format(HelpText['TRAFO'],
                  [Phrases['TERRAIN', fGrass + 12]]), pkTer, fGrass + 12,
                  hkTer, fGrass + 12, True);
              end;
            end;
            NextSection('SPECIAL');
            if No = 3 * 12 then
            begin
              LineFeed;
              for Special := 1 to 3 do
              begin
                if Special > 1 then
                  LineFeed;
                AddLine(Phrases['TERRAIN', 3 * 12 + Special], pkTer,
                  3 * 12 + Special);
              end;
            end
            else if (No < 12) and (No <> fGrass) and (No <> fOcean) then
            begin
              LineFeed;
              for Special := 1 to 2 do
                if (No <> fArctic) and (No <> fSwamp) or (Special < 2) then
                begin
                  if Special > 1 then
                    LineFeed;
                  AddLine(Phrases['TERRAIN', No + Special * 12], pkTer,
                    No + Special * 12);
                  I := FoodRes[Special] - FoodRes[0];
                  if I <> 0 then
                    MainText[Count - 1] := MainText[Count - 1] +
                      Format(HelpText['SPECIALFOOD'], [I]);
                  I := ProdRes[Special] - ProdRes[0];
                  if I <> 0 then
                    MainText[Count - 1] := MainText[Count - 1] +
                      Format(HelpText['SPECIALPROD'], [I]);
                  I := TradeRes[Special] - TradeRes[0];
                  if I <> 0 then
                    MainText[Count - 1] := MainText[Count - 1] +
                      Format(HelpText['SPECIALTRADE'], [I]);
                end;
            end;
            if No = 3 * 12 then
            begin
              LineFeed;
              AddTextual(HelpText['RARE']);
            end;
            if (No < 3 * 12) and (TerrType in [fDesert, fArctic]) then
            begin
              NextSection('SEEALSO');
              AddImprovement(woGardens);
              CheckSeeAlso := True;
            end;
          end;
        end;

      hkFeature:
        if No = 200 then
        begin // complete feature list
          Caption := HelpText['HELPTITLE_FEATURELIST'];
          List := THyperText.Create;
          List.OwnsObjects := True;
          for Special := 0 to 2 do
          begin
            if Special > 0 then
            begin
              LineFeed;
              LineFeed;
            end;
            case Special of
              0: AddLine(HelpText['HELPTITLE_FEATURE1LIST'], pkSection);
              1: AddLine(HelpText['HELPTITLE_FEATURE2LIST'], pkSection);
              2: AddLine(HelpText['HELPTITLE_FEATURE3LIST'], pkSection);
            end;
            List.Clear;
            for I := 0 to nFeature - 1 do
              if Feature[I].Preq <> preNA then
              begin
                if I < mcFirstNonCap then
                  J := 0
                else if I in AutoFeature then
                  J := 2
                else
                  J := 1;
                if J = Special then
                  List.AddLine(Phrases['FEATURES', I], pkFeature, I,
                    hkFeature, I);
              end;
            List.Sort;
            AppendList(List);
          end;
          FreeAndNil(List);
        end
        else
        begin // single feature
          Caption := Phrases['FEATURES', No];
          LineFeed;
          AddLine(Phrases['FEATURES', No], pkBigFeature, No);
          if No < mcFirstNonCap then
            AddLine(HelpText['HELPSPEC_CAP'])
          else if No in AutoFeature then
            AddLine(HelpText['HELPSPEC_STANDARD'])
          else
            AddLine(HelpText['HELPSPEC_FEATURE']);
          NextSection('EFFECT');
          AddTextual(HelpText.LookupByHandle(hFEATUREHELP, No));
          if (Feature[No].Weight <> 0) or (Feature[No].Cost <> 0) then
          begin
            NextSection('COSTS');
            S := IntToStr(Feature[No].Cost);
            if Feature[No].Cost >= 0 then
              S := '+' + S;
            AddLine(Format(HelpText['COSTBASE'], [S]));
            if Feature[No].Weight > 0 then
            begin
              AddLine(Format(HelpText['WEIGHT'],
                ['+' + IntToStr(Feature[No].Weight)]));
              if No = mcDefense then
                AddLine(Format(HelpText['WEIGHT'], ['+2']),
                  pkDomain, dGround);
            end;
          end;
          if Feature[No].Preq <> preNone then
          begin
            LineFeed;
            if Feature[No].Preq = preSun then
              AddPreqImp(woSun) // sun tsu feature
            else
              AddPreqAdv(Feature[No].Preq);
            MainText[Count - 1] := Format(HelpText['REQUIRED'],
              [MainText[Count - 1]]);
          end;
          NextSection('SEEALSO');
          CheckSeeAlso := True;
        end;

      hkModel:
        begin
          Caption := HelpText['HELPTITLE_MODELLIST'];
          for I := 0 to nSpecialModel - 1 do
            if I <> 2 then
              AddModelText(I);
          LineFeed;
          AddItem('MODELNOTE');
        end;

    end;
    if CheckSeeAlso then
      for I := 0 to Length(SeeAlso) - 1 do
        if (SeeAlso[I].Kind = Kind) and (SeeAlso[I].No = No) then
          case SeeAlso[I].SeeKind of
            hkImp: AddImprovement(SeeAlso[I].SeeNo);
            hkAdv: AddAdvance(SeeAlso[I].SeeNo);
            hkFeature: AddFeature(SeeAlso[I].SeeNo);
          end;
    if (Headline >= 0) and (Count = Headline + 1) then
      Delete(Headline)
    else
      LineFeed;

    ScrollBar.Init(Count - 1, InnerHeight div 24);
    ScrollBar.SetPos(sbPos);
    BackBtn.Visible := HistItems.Count > 1;
    TopBtn.Visible := (HistItems.Count > 1) or (Kind <> hkMisc) or (No <> Integer(miscMain));
    Sel := -1;
  end; // with MainText
end;

procedure THelpDlg.ShowNewContent(NewMode: TWindowMode; Category: TLinkCategory;
  Index: Integer);
begin
  if (Category <> Kind) or (Index <> No) or (Category = hkMisc) and
    (Index = Integer(miscSearchResult)) then begin
    if HistItems.Count = MaxHist then HistItems.Delete(0);
    if HistItems.Count = 0 then
      HistItems.AddNew(Category, Index, ScrollBar.Position, NewSearchContent)
      else HistItems.AddNew(Kind, No, ScrollBar.Position, SearchContent);
  end;
  Kind := Category;
  No := Index;
  SearchContent := NewSearchContent;
  Prepare;
  OffscreenPaint;
  inherited ShowNewContent(NewMode);
end;

procedure THelpDlg.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  i0, Sel0: Integer;
begin
  Y := Y - WideFrame;
  i0 := ScrollBar.Position;
  Sel0 := Sel;
  if (X >= SideFrame) and (X < SideFrame + InnerWidth) and (Y >= 0) and
    (Y < InnerHeight) and (Y mod 24 >= 8) then
    Sel := Y div 24
  else
    Sel := -1;
  if (Sel + i0 >= MainText.Count) or (Sel >= 0) and
    (THelpLineInfo(MainText.Objects[Sel + i0]).Category = hkNoLink) and
    (THelpLineInfo(MainText.Objects[Sel + i0]).Index = 0) then
    Sel := -1;
  if Sel <> Sel0 then
  begin
    if Sel0 <> -1 then
      Line(Canvas, Sel0, False);
    if Sel <> -1 then
      Line(Canvas, Sel, True);
  end;
end;

procedure THelpDlg.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Sel >= 0 then
    with THelpLineInfo(MainText.Objects[Sel + ScrollBar.Position]) do
      if Category = hkInternet then
        case Index of
          1: OpenDocument(GetAppSharePath(AITemplateFileName));
          2: OpenURL(CevoHomepage);
          3: OpenURL(CevoContact);
        end
      else
      begin
        if Index < 0 then Exit; // invalid link;
        ShowNewContent(FWindowMode, Category, Index);
      end;
end;

procedure THelpDlg.BackBtnClick(Sender: TObject);
var
  HistItem: THistItem;
begin
  if HistItems.Count > 1 then begin
    HistItem := THistItem.Create;
    HistItem.Assign(HistItems.Last);
    HistItems.Delete(HistItems.Count - 1);
    if (HistItem.Kind = hkMisc) and (HistItem.No = Integer(miscSearchResult)) and
      (HistItem.SearchContent <> SearchContent) then
    begin
      SearchContent := HistItem.SearchContent;
      Search(SearchContent);
    end;
    Kind := HistItem.Kind;
    No := HistItem.No;
    Prepare(HistItem.Pos);
    OffscreenPaint;
    Invalidate;
    FreeAndNil(HistItem);
  end;
end;

procedure THelpDlg.TopBtnClick(Sender: TObject);
begin
  while HistItems.Count > 1 do HistItems.Delete(HistItems.Count - 1);
  Kind := hkMisc;
  No := Integer(miscMain);
  Prepare;
  OffscreenPaint;
  Invalidate;
end;

procedure THelpDlg.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  ExtPic.Height := 0;
  inherited;
end;

function THelpDlg.TextIndex(Item: string): Integer;
begin
  Result := HelpText.GetHandle(Item);
end;

procedure THelpDlg.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if KeyToShortCut(Key, Shift) = BHelp.ShortCut then // my key
  else
    inherited;
end;

procedure THelpDlg.SearchBtnClick(Sender: TObject);
var
  InputDlg: TInputDlg;
begin
  InputDlg := TInputDlg.Create(nil);
  try
    InputDlg.Caption := Phrases['SEARCH'];
    InputDlg.EditInput.Text := SearchContent;
    InputDlg.CenterToRect(BoundsRect);
    InputDlg.ShowModal;
    if (InputDlg.ModalResult = mrOK) and (Length(InputDlg.EditInput.Text) >= 2) then
    begin
      Search(InputDlg.EditInput.Text);
      case SearchResult.Count of
        0: begin
          Gtk2Fix;
          SimpleMessage(Format(HelpText['NOMATCHES'],
            [InputDlg.EditInput.Text]));
        end;
        1:
          with THelpLineInfo(SearchResult.Objects[0]) do
            ShowNewContent(FWindowMode, Category, Index);
        else begin
          NewSearchContent := InputDlg.EditInput.Text;
          ShowNewContent(FWindowMode, hkMisc, Integer(miscSearchResult));
        end;
      end;
    end;
  finally
    InputDlg.Free;
  end;
end;

procedure THelpDlg.Search(SearchString: string);
var
  H, I, PrevHandle, PrevIndex, P, RightMargin: Integer;
  S: string;
  mADVHELP, mIMPHELP, mFEATUREHELP: set of 0..255;
  bGOVHELP, bSPECIALMODEL, bJOBHELP: Boolean;
begin
  SearchResult.Clear;
  mADVHELP := [];
  mIMPHELP := [];
  mFEATUREHELP := [];
  bGOVHELP := False;
  bSPECIALMODEL := False;
  bJOBHELP := False;

  // Search in generic reference
  SearchString := UpperCase(SearchString);
  for I := 0 to 35 + 4 do begin
    S := Phrases['TERRAIN', I];
    if Pos(SearchString, UpperCase(S)) > 0 then
      if I < 36 then
        SearchResult.AddLine(S + ' ' + HelpText['HELPSPEC_TER'],
          pkNormal, 0, hkTer, I, True)
      else
      begin
        SearchResult.AddLine(Phrases['TERRAIN', 36] + ' ' +
          HelpText['HELPSPEC_TER'], pkNormal, 0,
          hkTer, 36, True);
        if I > 36 then
          SearchResult.AddLine(Phrases['IMPROVEMENTS',
            imShipComp + I - 37] + ' ' + HelpText['HELPSPEC_SHIPPART'],
            pkNormal, 0, hkImp, imShipComp + I - 37, True);
        Break;
      end;
  end;
  for I := 0 to Length(JobHelp) - 1 do
    if Pos(SearchString, UpperCase(Phrases['JOBRESULT', JobHelp[I]])) > 0
    then
    begin
      SearchResult.AddLine(HelpText['HELPTITLE_JOBLIST'], pkNormal, 0,
        hkMisc, Integer(miscJobList), True);
      bJOBHELP := True;
      Break;
    end;
  for I := 0 to nAdv - 1 do
  begin
    S := Phrases['ADVANCES', I];
    if Pos(SearchString, UpperCase(S)) > 0 then
    begin
      if I in FutureTech then
        S := S + ' ' + HelpText['HELPSPEC_FUTURE']
      else
        S := S + ' ' + HelpText['HELPSPEC_ADV'];
      SearchResult.AddLine(S, pkNormal, 0, hkAdv, I, True);
      Include(mADVHELP, I);
    end;
  end;
  for I := 0 to nSpecialModel - 1 do
  begin
    FindStdModelPicture(SpecialModelPictureCode[I], H, S);
    if Pos(SearchString, UpperCase(S)) > 0 then
    begin
      SearchResult.AddLine(HelpText['HELPTITLE_MODELLIST'], pkNormal, 0,
        hkModel, 0, True);
      bSPECIALMODEL := True;
      Break;
    end;
  end;
  for I := 0 to nFeature - 1 do
  begin
    S := Phrases['FEATURES', I];
    if Pos(SearchString, UpperCase(S)) > 0 then
    begin
      if I < mcFirstNonCap then
        S := S + ' ' + HelpText['HELPSPEC_CAP']
      else if I in AutoFeature then
        S := S + ' ' + HelpText['HELPSPEC_STANDARD']
      else
        S := S + ' ' + HelpText['HELPSPEC_FEATURE'];
      SearchResult.AddLine(S, pkNormal, 0, hkFeature, I, True);
      Include(mFEATUREHELP, I);
    end;
  end;
  for I := 0 to nImp - 1 do
  begin
    S := Phrases['IMPROVEMENTS', I];
    if Pos(SearchString, UpperCase(S)) > 0 then
    begin
      case Imp[I].Kind of
        ikWonder:
          S := S + ' ' + HelpText['HELPSPEC_WONDER'];
        ikCommon:
          S := S + ' ' + HelpText['HELPSPEC_IMP'];
        ikShipPart:
          S := S + ' ' + HelpText['HELPSPEC_SHIPPART'];
      else
        S := S + ' ' + HelpText['HELPSPEC_NAT']
      end;
      SearchResult.AddLine(S, pkNormal, 0, hkImp, I, True);
      Include(mIMPHELP, I);
    end;
  end;
  for I := 0 to nGov - 1 do
    if Pos(SearchString, UpperCase(Phrases['GOVERNMENT', I])) > 0 then
    begin
      SearchResult.AddLine(HelpText['HELPTITLE_GOVLIST'], pkNormal, 0,
        hkMisc, Integer(miscGovList), True);
      bGOVHELP := True;
      Break;
    end;

  // Full text search
  H := -1;
  repeat
    PrevHandle := H;
    PrevIndex := I;
    if not HelpText.Search(SearchString, H, I) then
      Break;
    if H = hADVHELP then
    begin
      if (I >= 0) and ((I <> PrevIndex) or (H <> PrevHandle)) and
        not (I in mADVHELP) then
      begin
        S := Phrases['ADVANCES', I];
        if I in FutureTech then
          S := S + ' ' + HelpText['HELPSPEC_FUTURE']
        else
          S := S + ' ' + HelpText['HELPSPEC_ADV'];
        SearchResult.AddLine(S, pkNormal, 0, hkAdv, I, True);
      end;
    end
    else if H = hIMPHELP then
    begin
      if (I >= 0) and ((I <> PrevIndex) or (H <> PrevHandle)) and
        not (I in mIMPHELP) then
      begin
        S := Phrases['IMPROVEMENTS', I];
        case Imp[I].Kind of
          ikWonder:
            S := S + ' ' + HelpText['HELPSPEC_WONDER'];
          ikCommon:
            S := S + ' ' + HelpText['HELPSPEC_IMP'];
          ikShipPart:
            S := S + ' ' + HelpText['HELPSPEC_SHIPPART'];
        else
          S := S + ' ' + HelpText['HELPSPEC_NAT']
        end;
        SearchResult.AddLine(S, pkNormal, 0, hkImp, I, True);
      end;
    end
    else if H = hFEATUREHELP then
    begin
      if (I >= 0) and ((I <> PrevIndex) or (H <> PrevHandle)) and
        not (I in mFEATUREHELP) then
      begin
        S := Phrases['FEATURES', I];
        if I < mcFirstNonCap then
          S := S + ' ' + HelpText['HELPSPEC_CAP']
        else if I in AutoFeature then
          S := S + ' ' + HelpText['HELPSPEC_STANDARD']
        else
          S := S + ' ' + HelpText['HELPSPEC_FEATURE'];
        SearchResult.AddLine(S, pkNormal, 0, hkFeature, I, True);
      end;
    end
    else if H = hGOVHELP then
    begin
      if (I >= 0) and (H <> PrevHandle) and not bGOVHELP then
        SearchResult.AddLine(HelpText['HELPTITLE_GOVLIST'], pkNormal, 0,
          hkMisc, Integer(miscGovList), True);
    end
    else if H = hSPECIALMODEL then
    begin
      if (I >= 0) and (H <> PrevHandle) and not bSPECIALMODEL then
        SearchResult.AddLine(HelpText['HELPTITLE_MODELLIST'], pkNormal,
          0, hkModel, 0, True);
    end
    else if H = hJOBHELP then
    begin
      if (I >= 0) and (H <> PrevHandle) and not bJOBHELP then
        SearchResult.AddLine(HelpText['HELPTITLE_JOBLIST'], pkNormal, 0,
          hkMisc, Integer(miscJobList), True);
    end
    else if { (h<>hMAIN) and } (H <> PrevHandle) then
    begin
      S := HelpText.LookupByHandle(H);
      P := Pos('$', S);
      if P > 0 then
      begin
        S := Copy(S, P + 1, MaxInt);
        P := Pos('\', S);
        if P > 0 then
          S := Copy(S, 1, P - 1);
        SearchResult.AddLine(S, pkNormal, 0, hkText, H, True);
      end;
    end;
  until False;

  // Cut lines to fit to window
  RightMargin := InnerWidth - 16 - GetSystemMetrics(SM_CXVSCROLL);
  OffScreen.Canvas.Font.Assign(UniFont[ftNormal]);
  for I := 0 to SearchResult.Count - 1 do
  begin
    while BiColorTextWidth(OffScreen.Canvas, SearchResult[I]) >
      RightMargin - 32 do
      SearchResult[I] := Copy(SearchResult[I], 1, Length(SearchResult[I]) - 1);
  end;
end;

end.
