//
// misc.script
//

// _misc_GetPickedUnitHandle
//
function _misc_GetPickedUnitHandle() : Integer;
begin
   Result:=0;
   var i, h: Integer;
   if GetProjectModeInt=2 then begin //pmNone, pmEdit, pmRun
      for i:=0 to GetCountGameManagerPickedObjects-1 do begin
         h:=GetHandleGameManagerPickedObjects(i);
         if not IsGroupByHandle(h) then begin
            Result:=h;
            break;
         end;
      end;
      end else begin
      for i:=0 to GetCountEditManagerPickedObjects-1 do begin
         h:=GetHandleEditManagerPickedObjects(i);
         if not IsGroupByHandle(h) then begin
            Result:=h;
            break;
         end;
      end;
   end;
end;

// _misc_GetObjectArgData
//
function _misc_GetObjectArgData(goHnd, argInd : Integer) : Pointer;
begin
   if goHnd <> 0 then
   begin
      var hnd : Integer = GetGameObjectStateMachineHandle(goHnd);
      if hnd <> 0 then
      begin
         Result := StateMachineGetArgDataByInd(hnd, argInd);
         if (gProfile.bDbgLogPointerNil) then
         _init_DebugPointerNil(Result);
      end else Result := nil;
   end else Result := nil;
end;

// _misc_GetBaseObjID
//
function _misc_GetBaseObjID(const hnd: Integer): Integer;
begin
   if hnd<>0 then
   begin
      var smhnd : Integer = GetGameObjectStateMachineHandle(hnd);
      if smhnd<>0 then
      begin
         var parg: Pointer = StateMachineGetArgDataByInd(smhnd, gc_argunit_obj);
         if parg<>nil then
         Result:=TBaseObj(parg).baseid
         else begin
            Result:=gc_baseid_nil;
            //ErrorLog('pointer=nil : _misc_GetBaseObjID : state='+GetCurrentStateName+' filename='+GetStateMachineFileName(GetCurrentStateMachineHandle)+' hnd='+IntToStr(hnd));
         end;
      end else Result:=gc_baseid_nil;
   end else Result:=gc_baseid_nil;
end;

// _misc_GetMapLeftTopPos
//
procedure _misc_GetMapLeftTopPos(var x, y : Float);
begin
   x := -(GetMapWidth div 2);
   y := -(GetMapHeight div 2);
end;

// _misc_NormalizePosToMap
//
procedure _misc_NormalizePosToMap(var x, y : Float);
begin
   var hw : Integer = GetMapWidth div 2;
   var hh : Integer = GetMapHeight div 2;
   if x<=-hw then x:=-hw+gc_epsilon
   else
   if x>=hw then x:=hw-gc_epsilon;
   if y<=-hh then y:=-hh+gc_epsilon
   else
   if y>=hh then y:=hh-gc_epsilon;
end;

// _misc_NormalizePosToMapInt
//
procedure _misc_NormalizePosToMapInt(var x, y : Integer);
begin
   var hw : Integer = GetMapWidth div 2;
   var hh : Integer = GetMapHeight div 2;
   if x<-hw then x:=-hw
   else
   if x>=hw then x:=hw-1;
   if y<-hh then y:=-hh
   else
   if y>=hh then y:=hh-1;
end;

// _misc_GetIsland
//
function _misc_GetIsland(x, z : Float) : Integer;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   var ix : Integer = Floor(2*x + w);
   var iy : Integer = Floor(2*z + h);
   Result := gIslands.grid[ix, iy].index;
end;

// _misc_GetIslandInRadius
//
function _misc_GetIslandInRadius(x, z : Float; rad : Integer) : Integer;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   var ix : Integer = Floor(2*x + w);
   var iy : Integer = Floor(2*z + h);
   Result := gIslands.grid[ix, iy].index;
   if Result < 0 then
   begin
      var r : Integer = 2*rad;
      var i, j : Integer;
      var minDist : Float = gc_MaxFloat;
      
      for i := -r to r do
      for j := -r to r do
      begin
         var i1 : Integer = ix+i;
         var j1 : Integer = iy+j;
         var isl : Integer = gIslands.grid[i1, j1].index;
         if IsInRangeInt(i1, 0, 2*w-1) and IsInRangeInt(j1, 0, 2*h-1) and (isl >= 0) then
         begin
            var dist : Float = VectorDistance(x, 0, z, (i1-w)/2, 0, (j1-h)/2);
            if dist < minDist then
            begin
               minDist := dist;
               Result := isl;
            end;
         end;
      end;
   end;
end;

// _misc_ProfilerReport
//
procedure _misc_ProfilerReport();
begin
   EditorProfManagerStop;
   gbool_profiler_enabled := False;
   EditorProfManagerShowReport;
   EditorProfManagerStart;
   gbool_profiler_enabled := True;
end;

{// _misc_ProfilerInit
//
procedure _misc_ProfilerInit();
begin
   EditorProfManagerStart;
   gbool_profiler_enabled := True;
end;

// _misc_ProfilerShowReport
//
procedure _misc_ProfilerShowReport(const bStop : Boolean);
begin
   if (bStop) then
   begin
      EditorProfManagerStop;
      gbool_profiler_enabled := False;
   end;
   EditorProfManagerShowReport;
end;}

// _misc_ProfilerBegin
//
procedure _misc_ProfilerBegin(const sid : String);
begin
   if (gbool_profiler_enabled) then
   begin
      var profHnd : Integer = EditorProfManagerGetProfilerHandleByName(sid);
      if (profHnd=0) then
      profHnd := EditorProfManagerCreateProfiler(sid);
      EditorProfilerBegin(profHnd);
   end;
end;

// _misc_ProfilerEnd
//
procedure _misc_ProfilerEnd(const sid : String);
begin
   if (gbool_profiler_enabled) then
   begin
      var profHnd : Integer = EditorProfManagerGetProfilerHandleByName(sid);
      if (profHnd<>0) then
      EditorProfilerEnd(profHnd);
   end;
end;

// _misc_ProfilerBeginHnd
//
function _misc_ProfilerBeginHnd(const sid : String) : Integer;
begin
   if (gbool_profiler_enabled) then
   begin
      Result := EditorProfManagerGetProfilerHandleByName(sid);
      if (Result=0) then
      Result := EditorProfManagerCreateProfiler(sid);
      EditorProfilerBegin(Result);
   end;
end;

// _misc_ProfilerEndHnd
//
procedure _misc_ProfilerEndHnd(const profHnd : Integer);
begin
   if (gbool_profiler_enabled) and (profHnd<>0) then
   EditorProfilerEnd(profHnd);
end;

// _misc_LogByteAsBits
//
procedure _misc_LogByteAsBits(var s0 : String; b : byte);
begin
   var s : String;
   var i : Integer;
   for i:=7 downto 0 do s:=s+IntToStr((b shr i) and $01);
   log(s0+s);
end;

// _misc_SaveVariableToFile
//
procedure _misc_SaveVariableToFile(varname : String);
begin
   var filepath : String = ExtractFilePath(varname);
   var svarname : String = ExtractFileName(varname);
   var ptmp : Integer = _parser_ParserTemporary(true);
   StateMachineGlobalVariablesSaveToParser(ptmp, svarname, False, False);
   ParserSaveToFileByHandle(ptmp, filepath+svarname);
end;

// _misc_SaveVariableToFileName
//
procedure _misc_SaveVariableToFileName(varname, filename : String);
begin
   var ptmp : Integer = _parser_ParserTemporary(true);
   StateMachineGlobalVariablesSaveToParser(ptmp, varname, False, False);
   ParserSaveToFileByHandle(ptmp, filename);
end;

// _misc_LoadVariableFromFile
//
procedure _misc_LoadVariableFromFile(varname : String);
begin
   var ptmp : Integer = _parser_ParserTemporary(true);
   ParserLoadFromFileByHandle(ptmp, varname);
   StateMachineGlobalVariablesLoadFromParser(ptmp, varname, False, False);
end;

// _misc_LoadVariableFromFileName
//
procedure _misc_LoadVariableFromFileName(varname, filename : String);
begin
   var ptmp : Integer = _parser_ParserTemporary(true);
   ParserLoadFromFileByHandle(ptmp, filename);
   StateMachineGlobalVariablesLoadFromParser(ptmp, varname, False, False);
end;

// _misc_SaveObjToFile
//
procedure _misc_SaveObjToFile(goHnd : Integer; const filename : String);
begin
   // debug function
   if (goHnd<>0) then
   begin
      var sfilename : String = filename;
      if (sfilename='') then
      sfilename := 'gDbgObj';
      
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      gDbgObj := TObj(pobj);
      var pTmp : Integer = _parser_ParserTemporary(True);
      StateMachineGlobalVariablesSaveToParser(pTmp, 'gDbgObj', False, False);
      ParserSaveToFileByHandle(pTmp, '.\log\'+sfilename);
      
      var pPlayers : Integer = _parser_ParserTemporary(True);
      StateMachineGlobalVariablesSaveToParser(pPlayers, 'gPlayer', False, False);
      var pPlayer : Integer = ParserSelectByHandleByIndex(pPlayers, TObj(pobj).pl);
      var pObjBase : Integer = ParserSelectByHandleByKey(pPlayer, 'objbase');
      pObjBase := ParserSelectByHandleByIndex(pObjBase, TObj(pobj).cid);
      pObjBase := ParserSelectByHandleByIndex(pObjBase, TObj(pobj).id);
      ParserSaveToFileByHandle(pObjBase, '.\log\'+sfilename+'B');
   end;
end;

// _misc_SaveObjToFileDefault
//
procedure _misc_SaveObjToFileDefault();
begin
   // debug function
   _misc_SaveObjToFile(_misc_GetPickedUnitHandle, '');
end;

// _misc_SavePlayerToFile
//
procedure _misc_SavePlayerToFile(plHnd : Integer; const filename : String);
begin
   // debug function
   if (plHnd<>0) then
   begin
      var plInd : Integer = GetPlayerIndexByHandle(plHnd);
      var pPlayers : Integer = _parser_ParserTemporary(True);
      StateMachineGlobalVariablesSaveToParser(pPlayers, 'gPlayer', False, False);
      var pPlayer : Integer = ParserSelectByHandleByIndex(pPlayers, plInd);
      var pObjBase : Integer = ParserSelectByHandleByKey(pPlayer, 'objbase');
      ParserFreeByHandle(pObjBase);
      
      var sfilename : String = filename;
      if (sfilename='') then
      sfilename := 'gPlayer';
      ParserSaveToFileByHandle(pPlayer, '.\log\'+sfilename);
   end;
end;

// _misc_SavePlayerToFileDefault
//
procedure _misc_SavePlayerToFileDefault();
begin
   // debug function
   _misc_SavePlayerToFile(GetPlayerHandleInterfaceIO, '');
end;

// _misc_SaveCountryToFile
//
procedure _misc_SaveCountryToFile(cid : Integer; const filter, filename : String);
begin
   // debug function
   var pCountries : Integer = _parser_ParserTemporary(True);
   StateMachineGlobalVariablesSaveToParser(pCountries, 'gCountry', False, False);
   var pCountry : Integer = ParserSelectByHandleByIndex(pCountries, cid);
   var pSave : Integer;
   if (filter<>'') then
   pSave := ParserSelectByHandleByKey(pCountry, filter)
   else
   pSave := pCountry;
   
   var sfilename : String = filename;
   if (sfilename='') then
   sfilename := 'gCountry';
   ParserSaveToFileByHandle(pSave, '.\log\'+sfilename);
end;

// _misc_SaveCountryToFileDefault
//
procedure _misc_SaveCountryToFileDefault(const filter : String);
begin
   // debug function
   var plHnd : Integer = GetPlayerHandleInterfaceIO;
   if (plHnd<>0) then
   begin
      var plInd : Integer = GetPlayerIndexByHandle(plHnd);
      _misc_SaveCountryToFile(gPlayer[plInd].cid, filter, '');
   end;
end;

// _misc_RandomizePickedUnitStateMachineInterval
//
procedure _misc_RandomizePickedUnitStateMachineInterval(min, max : Integer);
begin
   // debug function
   var plHnd : Integer;
   var plInd : Integer;
   var goInd : Integer;
   var goHnd : Integer;
   for plInd := 0 to GetCountOfPlayers-1 do
   begin
      plHnd := GetPlayerHandleByIndex(plInd);
      for goInd := GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         goHnd := GetGameObjectHandleByIndex(goInd, plHnd);
         if (GetGameObjectPickedByHandle(goHnd)) then
         SetGameObjectStateMachineIntervalByHandle(goHnd, min+(goInd mod (max-min)));
      end;
   end;
end;

//_misc_SwitchInt
//
function _misc_SwitchInt(const falseval, trueval : Integer; const bswitch : Boolean) : Integer;
begin
   if (bswitch) then
   Result := trueval
   else
   Result := falseval;
end;

//_misc_SwitchFloat
//
function _misc_SwitchFloat(const falseval, trueval : Float; const bswitch : Boolean) : Float;
begin
   if (bswitch) then
   Result := trueval
   else
   Result := falseval;
end;

//_misc_SwitchString
//
procedure _misc_SwitchString(var text : String; const falseval, trueval : String; const bswitch : Boolean);
begin
   if (bswitch) then
   text := trueval
   else
   text := falseval;
end;

// _misc_ClampInt
//
function _misc_ClampInt(const val, min, max : Integer) : Integer;
begin
   if (val<min) then Result := min
   else
   if (val>max) then Result := max
   else
   Result := val;
end;

// _misc_MinFloat
//
function _misc_MinFloat(const a, b : Float) : Float;
begin
   if (a<b) then
   Result := a
   else
   Result := b;
end;

// _misc_MaxFloat
//
function _misc_MaxFloat(const a, b : Float) : Float;
begin
   if (a>b) then
   Result := a
   else
   Result := b;
end;

// _misc_RoundUp
//
function _misc_RoundUp(const a : Float) : Integer;
begin
   Result := Round(a);
   if (a>Result) then
   Result := Result+1;
end;

// _misc_PixelsToTiles
//
function _misc_PixelsToTiles(const val : Integer) : Float;
begin
   // конвертация значений оригинальных казаках в величины движка
   Result := (val/gc_pixels_to_tile);
end;

// _misc_FramesToTime
//
function _misc_FramesToTime(const val : Float) : Float;
begin
   // конвертация значений оригинальных казаках в величины движка
   Result := (val*gc_frames_to_time);
end;

// _misc_BuildtimeToTime
//
function _misc_BuildtimeToTime(const val : Integer) : Float;
begin
   // конвертация значений оригинальных казаках в величины движка
   Result := (val*gc_frames_to_time*gc_buildtime_modifier);
end;

// _misc_RandToRandom
//
function _misc_RandToRandom(const val : Integer) : Float;
begin
   // конвертация значений оригинальных казаках в величины движка
   Result := (val/gc_c1rand_to_random);
end;

// _misc_RandomInt
//
function _misc_RandomInt() : Integer;
begin
   // конвертация значений оригинальных казаках в величины движка
   Result := floor(random*gc_c1rand_to_random);
end;

// _misc_VectorLerpDist
//
procedure _misc_VectorLerpDist(x1, y1, x2, y2, dist : Float; var outx, outy : Float);
begin
   var dirx : Float = x2-x1;
   var diry : Float = y2-y1;
   var temp : Float;
   VectorNormalize(dirx, temp, diry);
   outx := x1 + dist*dirx;
   outy := y1 + dist*diry;
end;

// _misc_TopZonePathLerp
//
function _misc_TopZonePathLerp(endInd : Integer; dist : Float; flip : Boolean; var x, z : Float) : Boolean;
begin
   Result := false;
   if IsInRangeInt(endInd, 0, TopologyGetZonesCount-1) then
   begin
      var curDist : Float;
      var tpCount : Integer = TopologyGetZoneTrackPointsCountByIndex(endInd);
      if tpCount > 1 then
      begin
         var prevX, prevZ : Float;
         var curInd : Integer;
         var delta : Integer;
         
         if flip then
         begin
            curInd := tpCount-2;
            delta := -1;
            TopologyGetZoneTrackPointCoordsByIndex(endInd, tpCount-1, prevX, prevZ);
         end
         else
         begin
            curInd := 1;
            delta := 1;
            TopologyGetZoneTrackPointCoordsByIndex(endInd, 0, prevX, prevZ);
         end;
         
         while (curInd >= 0) and (curInd <= tpCount-1) do
         begin
            var curX, curZ : Float;
            TopologyGetZoneTrackPointCoordsByIndex(endInd, curInd, curX, curZ);
            
            var d : Float = VectorDistance(curX, 0, curZ, prevX, 0, prevZ);
            if curDist + d >= dist then
            begin
               var x1, z1 : Float;
               _misc_VectorLerpDist(prevX, prevZ, curX, curZ, curDist+d-dist, x1, z1);
               x := x1;
               z := z1;
               
               if TopologyGetZoneIndex(x, z) < 0 then
               begin
                  x := curX;
                  z := curZ;
               end;
               
               Result := true;
               break;
            end
            else
            curDist := curDist + d;
            
            prevX := curX;
            prevZ := curZ;
            
            curInd := curInd + delta;
         end;
         
         if (not Result) and (tpCount > 1) then
         begin
            if not flip then
            TopologyGetZoneTrackPointCoordsByIndex(endInd, tpCount-1, x, z)
            else
            TopologyGetZoneTrackPointCoordsByIndex(endInd, 0, x, z);
            
            Result := true;
         end;
      end;
   end;
end;

// _misc_GameObjectRandomizeDir
//
procedure _misc_GameObjectRandomizeDir();
begin
   // debug function
   var plHnd : Integer;
   var plInd : Integer;
   var goInd : Integer;
   var goHnd : Integer;
   for plInd:=0 to GetCountOfPlayers-1 do
   begin
      plHnd := GetPlayerHandleByIndex(plInd);
      for goInd:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         goHnd := GetGameObjectHandleByIndex(goInd, plHnd);
         if GetGameObjectPickedByHandle(goHnd) then
         begin
            GameObjectRollByHandle(goHnd, random*360)
         end;
      end;
   end;
end;

// _misc_GameObjectRandomizePosInCircle
//
procedure _misc_GameObjectRandomizePosInCircle(x, y, radius : Float);
begin
   // debug function
   var plHnd : Integer;
   var plInd : Integer;
   var goInd : Integer;
   var goHnd : Integer;
   for plInd:=0 to GetCountOfPlayers-1 do
   begin
      plHnd := GetPlayerHandleByIndex(plInd);
      for goInd:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         goHnd := GetGameObjectHandleByIndex(goInd, plHnd);
         if GetGameObjectPickedByHandle(goHnd) then
         begin
            var i : Integer;
            for i:=0 to 100-1 do // try to place several times
            begin
               var offX : Float = (0.5-random)*radius*2;
               var offY : Float = (0.5-random)*radius*2;
               if (VectorLength(offX, 0, offY)<=radius) then
               begin
                  SetGameObjectPositionByHandle(goHnd, x+offX, 0, y+offY);
                  break;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_GameObjectRandomizePosInRectangle
//
procedure _misc_GameObjectRandomizePosInRectangle(x, y, width, height : Float);
begin
   // debug function
   var plHnd : Integer;
   var plInd : Integer;
   var goInd : Integer;
   var goHnd : Integer;
   for plInd:=0 to GetCountOfPlayers-1 do
   begin
      plHnd := GetPlayerHandleByIndex(plInd);
      for goInd:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         goHnd := GetGameObjectHandleByIndex(goInd, plHnd);
         if GetGameObjectPickedByHandle(goHnd) then
         SetGameObjectPositionByHandle(goHnd, x+(0.5-random)*width, 0, y+(0.5-random)*height);
      end;
   end;
end;

// _misc_GetDistancePos
//
function _misc_GetDistancePos(var pos1, pos2 : TPos3f; b3D : Boolean) : Float;
begin
   if (b3D) then
   Result := VectorDistance(pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z)
   else
   Result := VectorDistance(pos1.x, 0, pos1.y, pos2.x, 0, pos2.y);
end;

// _misc_CreateEffectZTest
//
procedure _misc_CreateEffectZTest(goHnd : Integer; bState : Boolean);
begin
   const className = 'TXZBufferEffect';
   var effHnd : Integer = EffectCreate(goHnd, className, true, false);
   SetBehaviourBoolProperty(effHnd, 'ZWrite', True);
   SetBehaviourBoolProperty(effHnd, 'ZTest', bState);
   SetBehaviourBoolProperty(effHnd, 'ColorBuffer', True);
   SetBehaviourBoolProperty(effHnd, 'NoColorBuffer', True);
end;

// _misc_GetDelimiterString
//
procedure _misc_GetDelimiterString(const str : String; const delimiterchar : Char; var s1, s2, s3, s4 : String);
begin
   var dc : Char = delimiterchar;
   if (dc=0) then
   dc := gc_gui_delimiterchar;
   var delimiterStringCount : Integer = GetDelimiterStringCount(str, dc, gc_gui_quotechar);
   s1 := GetDelimiterStringByIndex(str, dc, gc_gui_quotechar, 0);
   if (delimiterStringCount>=2) then
   s2 := GetDelimiterStringByIndex(str, dc, gc_gui_quotechar, 1);
   if (delimiterStringCount>=3) then
   s3 := GetDelimiterStringByIndex(str, dc, gc_gui_quotechar, 2);
   if (delimiterStringCount>=4) then
   s4 := GetDelimiterStringByIndex(str, dc, gc_gui_quotechar, 3);
end;

// _misc_ConstStringToText
//
function _misc_ConstStringToText(const str : String; var text : String) : Boolean;
begin
   Result := False;
   if (IsDelimiterCharExists(str, gc_gui_delimiterchar)) then
   begin
      var delimiterStringCount : Integer = GetDelimiterStringCount(str, gc_gui_delimiterchar, gc_gui_quotechar);
      if (delimiterStringCount>=2) then
      begin
         var tableID : String = GetDelimiterStringByIndex(str, gc_gui_delimiterchar, gc_gui_quotechar, 0);
         var keyID : String = GetDelimiterStringByIndex(str, gc_gui_delimiterchar, gc_gui_quotechar, 1);
         if (tableID<>'') and (keyID<>'') then
         begin
            text := GetLocaleTableListItemByID(tableID, keyID);
            if (text='') then
            text := str
            else
            Result := True;
         end
         else
         text := str;
      end
      else
      text := str;
   end
   else
   text := str;
end;

// _misc_StringToText
//
function _misc_StringToText(var str, text : String) : Boolean;
begin
   Result := False;
   if (IsDelimiterCharExists(str, gc_gui_delimiterchar)) then
   begin
      var delimiterStringCount : Integer = GetDelimiterStringCount(str, gc_gui_delimiterchar, gc_gui_quotechar);
      if (delimiterStringCount>=2) then
      begin
         var tableID : String = GetDelimiterStringByIndex(str, gc_gui_delimiterchar, gc_gui_quotechar, 0);
         var keyID : String = GetDelimiterStringByIndex(str, gc_gui_delimiterchar, gc_gui_quotechar, 1);
         if (tableID<>'') and (keyID<>'') then
         begin
            text := GetLocaleTableListItemByID(tableID, keyID);
            if (text='') then
            text := str
            else
            Result := True;
         end
         else
         text := str;
      end
      else
      text := str;
   end
   else
   text := str;
end;

// _misc_PrepareLogMessageNotEnoughResourcesText
//
procedure _misc_PrepareLogMessageNotEnoughResourcesText(var text : String; food, wood, stone, gold, iron, coal : Integer);
begin
   // вспомогательная функция. составляет текст со списком ресурсов, которые не хватает игроку. Исользуется в игровых сообщениях
   var plInd : Integer = GetPlayerIndexByHandle(GetPlayerHandleInterfaceIO);
   if (gPlayer[plInd].res[gc_resource_type_food]<food) then
   text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_food))+': '+IntToStr(food);
   if (gPlayer[plInd].res[gc_resource_type_wood]<wood) then
   begin
      if (text<>'') then
      text := text+'. ';
      text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_wood))+' - '+IntToStr(wood);
   end;
   if (gPlayer[plInd].res[gc_resource_type_stone]<stone) then
   begin
      if (text<>'') then
      text := text+'. ';
      text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_stone))+' - '+IntToStr(stone);
   end;
   if (gPlayer[plInd].res[gc_resource_type_gold]<gold) then
   begin
      if (text<>'') then
      text := text+'. ';
      text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_gold))+': '+IntToStr(gold);
   end;
   if (gPlayer[plInd].res[gc_resource_type_iron]<iron) then
   begin
      if (text<>'') then
      text := text+'. ';
      text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_iron))+': '+IntToStr(iron);
   end;
   if (gPlayer[plInd].res[gc_resource_type_coal]<coal) then
   begin
      if (text<>'') then
      text := text+'. ';
      text := text+GetLocaleTableListItemByID(gc_loc_gui, 'restype.'+IntToStr(gc_resource_type_coal))+': '+IntToStr(coal);
   end;
end;

// _misc_GetUnitNameBySID
//
function _misc_GetUnitNameBySID(var sid : String; var text : String) : Boolean;
begin
   // возвращает текстовое имя юнита
   Result := True;
   var hintid : String = gc_loc_units+'|'+sid;
   if (not _misc_StringToText(hintid, text)) then
   begin
      var tmp : String = SubStr(sid, 4, StrLength(text)-3);
      hintid := gc_loc_units+'|'+'%nat%'+tmp;
      if (not _misc_StringToText(hintid, text)) then
      begin
         hintid := gc_loc_units+'|'+'%com%'+tmp;
         if (not _misc_StringToText(hintid, text)) then
         begin
            hintid := 'misc|'+sid;
            if (not _misc_StringToText(hintid, text)) then
            begin
               hintid := 'misc|%nat%'+tmp;
               if (not _misc_StringToText(hintid, text)) then
               begin
                  hintid := 'misc|%com%'+tmp;
                  if (not _misc_StringToText(hintid, text)) then
                  begin
                     text := sid;
                     Result := False;
                  end;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_GetUnitDescriptionBySID
//
function _misc_GetUnitDescriptionBySID(var sid : String; var text : String) : Boolean;
begin
   // возвращает текстовое имя юнита
   var suffix : String = '.ext';
   var hintid : String = gc_loc_units+'|'+sid+suffix;
   if (not _misc_StringToText(hintid, text)) then
   begin
      var tmp : String = SubStr(sid, 4, StrLength(text)-3);
      hintid := gc_loc_units+'|'+'%nat%'+tmp+suffix;
      if (not _misc_StringToText(hintid, text)) then
      begin
         hintid := gc_loc_units+'|'+'%com%'+tmp+suffix;
         if (not _misc_StringToText(hintid, text)) then
         begin
            hintid := 'misc|'+sid+suffix;
            if (not _misc_StringToText(hintid, text)) then
            begin
               hintid := 'misc|%nat%'+tmp+suffix;
               if (not _misc_StringToText(hintid, text)) then
               begin
                  hintid := 'misc|%com%'+tmp+suffix;
                  if (not _misc_StringToText(hintid, text)) then
                  text := '';
                  //text := sid; // show nothing in no description
               end;
            end;
         end;
      end;
   end;
end;

// _misc_ShowLogMessage
//
procedure _misc_ShowLogMessage(const id : Integer; var s1, s2 : String; const ballowdublicates : Boolean);
begin
   if (not ballowdublicates) then
   begin
      var bresort : Boolean;
      var i : Integer;
      for i:=0 to gc_MaxLogMessageCount-1 do
      begin
         if (id=gc_logmessagesid_unitinfo) then
         begin
            if (gLogMessages.msg[i].id=gc_logmessagesid_unitinfo) then
            begin
               gLogMessages.Clear(i);
               bresort := True;
            end;
         end
         else
         if (not gLogMessages.msg[i].ballowdublicates) and (gLogMessages.msg[i].id=id) and ((gLogMessages.msg[i].s1=s1) and (gLogMessages.msg[i].s2=s2)) then
         begin
            if ((GetCurrentTime-gLogMessages.msg[i].time)>2) then
            gLogMessages.msg[i].time := GetCurrentTime-2;
            exit;
         end;
      end;
      if (bresort) then
      begin
         gLogMessages.Sort;
         gLogMessages.CalcCount;
      end;
   end;
   var ind : Integer = gLogMessages.count;
   if (gLogMessages.count>=gc_MaxLogMessageCount) then
   exit;
   gLogMessages.msg[ind].id := id;
   gLogMessages.msg[ind].s1 := s1;
   gLogMessages.msg[ind].s2 := s2;
   if (id<>gc_logmessagesid_unitinfo) then
   gLogMessages.msg[ind].time := GetCurrentTime
   else
   gLogMessages.msg[ind].time := GetCurrentTime-5;
   gLogMessages.msg[ind].ballowdublicates := ballowdublicates;
   gLogMessages.count := ind+1;
end;

// _misc_GetUnitIconNameBySID
//
procedure _misc_GetUnitIconNameBySID(var sid, icon : String);
begin
   if (GetGUITextureWidth(gc_hud_icons_units+sid)<>0) then
   icon := gc_hud_icons_units+sid
   else
   if (GetGUITextureWidth(gc_hud_icons_bld+sid)<>0) then
   icon := gc_hud_icons_bld+sid
   else
   begin
      var bldname : String = SubStr(sid, 4, StrLength(sid)-3);
      icon := gc_hud_icons_bld+bldname;
   end;
end;

// _misc_GetUnitType
//
function _misc_GetUnitType(const goHnd : Integer) : Integer;
begin
   // gc_result_unittype_inf = 0;
   // gc_result_unittype_cav = 1;
   // gc_result_unittype_art = 2;
   // gc_result_unittype_ship = 3;
   var unittype : Integer = gc_result_unittype_inf;
   var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if (pobj<>nil) then
   begin
      if (gObjProp[TObj(pobj).cid][TObj(pobj).id].bArtillery) then
      unittype := gc_result_unittype_art
      else
      case (gObjProp[TObj(pobj).cid][TObj(pobj).id].usage) of
         //gc_obj_usage_cannon, gc_obj_usage_mortar, gc_obj_usage_supermortar, gc_obj_usage_mcannon : unittype := gc_result_unittype_art;
         gc_obj_usage_fasthorse, gc_obj_usage_hardhorse, gc_obj_usage_horseshooter : unittype := gc_result_unittype_cav;
         gc_obj_usage_fisher, gc_obj_usage_battleship, gc_obj_usage_frigate, gc_obj_usage_galley, gc_obj_usage_yacht, gc_obj_usage_xebec, gc_obj_usage_transport : unittype := gc_result_unittype_ship;
      end;
   end;
   Result := unittype;
end;

// _misc_GetUnitIconNameByID
//
procedure _misc_GetUnitIconNameByID(cid, id : Integer; var icon : String);
begin
   const cPlInd = 0;
   var prefix : String;
   if (gObjProp[cid][id].bbuilding) then
   prefix := gc_hud_icons_bld
   else
   prefix := gc_hud_icons_units;
   if (gObjProp[cid][id].bbuilding) and (GetGUITextureWidth(prefix+gObjProp[cid][id].sid)=0) then
   begin
      var bldname : String = SubStr(gObjProp[cid][id].sid, 4, StrLength(gObjProp[cid][id].sid)-3);
      icon := prefix+bldname;
   end
   else
   icon := prefix+gObjProp[cid][id].sid;
end;

// _misc_GetUpgradeIconNameBySID
//
procedure _misc_GetUpgradeIconNameBySID(var upgsid : String; var icon : String);
begin
   var i, j : Integer;
   var charCount : Integer;
   for [MAIN]i:=0 to 1 do
   begin
      var bnum : Boolean = (i=0);
      if (bnum) then
      charCount := 4
      else
      charCount := 3;
      var tmp : String;
      for j:=0 to 4 do
      begin
         case j of
            0 : begin
               icon := gc_hud_icons_upg+upgsid;
            end;
            1 : begin
               tmp := SubStr(upgsid, 4, StrLength(upgsid)-charCount);
               icon := gc_hud_icons_upg+'%nat%'+tmp;
            end;
            2 : begin
               tmp := SubStr(upgsid, 4, StrLength(upgsid)-charCount);
               icon := gc_hud_icons_upg+'%com%'+tmp;
            end;
            3 : begin
               tmp := SubStr(upgsid, StrLength(upgsid)-3, 4);
               icon := gc_hud_icons_upg+'%member%'+tmp;
            end;
            4 : begin
               icon := 'noname';
            end;
         end;
         if (bnum) then
         icon := icon+'%num%';
         if (GetGUITextureWidth(icon)<>0) then
         break(MAIN);
      end;
   end;
end;

// _misc_UnitCreateMiniMapPrimitive
//
function _misc_UnitCreateMiniMapPrimitive(goHnd : Integer; bSetAsSelected : Boolean) : Integer;
begin
   if (GetGameObjectVisibleByHandle(goHnd)) then
   begin
      var primname : String;
      var bbuilding : Boolean;
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) then
      bbuilding := gObjProp[TObj(pobj).cid][TObj(pobj).id].bbuilding;
      if (bbuilding) then
      begin
         if (bSetAsSelected) then
         primname := 'pl3x3.sel'
         else
         primname := 'pl3x3.'+IntToStr(gMap.players[TObj(pobj).pl].color);
      end
      else
      begin
         if (bSetAsSelected) then
         primname := 'pl2x2.sel'
         else
         primname := 'pl2x2.'+IntToStr(gMap.players[TObj(pobj).pl].color);
      end;
      var behHnd : Integer = BehaviourCreateWithKey(goHnd, 'TXMiniMapPrimitiveBehaviour', 'prim', false);
      SetBehaviourStringProperty(behHnd, 'GetOrCreatePrimitive', primname);
      SetBehaviourIntProperty(behHnd, 'Interval', 50);
      SetBehaviourBoolProperty(behHnd, 'AutoDestroy', True);
      SetBehaviourBoolProperty(behHnd, 'UpdateDirection', False);
      SetBehaviourBoolProperty(behHnd, 'UseCustomDir', True);
      SetBehaviourAffineVectorProperty(behHnd, 'CustomDir', 1, 0, 0);
      Result := behHnd;
   end
   else
   Result := 0;
end;

// _misc_UnitRemoveMiniMapPrimitive
//
procedure _misc_UnitRemoveMiniMapPrimitive(goHnd : Integer);
begin
   var behHnd : Integer = GetBehaviourByKey(goHnd, 'prim');
   if (behHnd<>0) then
   begin
      SetBehaviourStringProperty(behHnd, 'ReleasePrimitive', '');
      BehaviourDestroy(behHnd);
   end;
end;

// _misc_UpdateMiniMapPrimitives
//
procedure _misc_UpdateMiniMapPrimitives(bSkipUnits : Boolean);
begin
   var plHnd : Integer;
   var plInd : Integer;
   var goInd : Integer;
   var goHnd : Integer;
   var myPlHnd : Integer = GetPlayerHandleInterfaceIO;
   if (myPlHnd<>0) then
   begin
      var myPlInd : Integer = GetPlayerIndexByHandle(myPlHnd);
      for plInd:=0 to gc_MaxPlayerCount-1 do
      begin
         plHnd := GetPlayerHandleByIndex(plInd);
         if (GetPlayerRaceTagByHandle(plHnd)=gc_racetag_units) then
         begin
            for goInd:=0 to GetPlayerGameObjectsCountByHandle(plHnd)-1 do
            begin
               goHnd := GetGameObjectHandleByIndex(goInd, plHnd);
               var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
               if (pobj<>nil) and (not TObj(pobj).bdead) and ((not bSkipUnits) or (gObjProp[TObj(pobj).cid][TObj(pobj).id].bbuilding)) {and GetGameObjectVisibleByHandle(goHnd)} then
               gGuiUpdateHighlights.Add(goHnd);
            end;
         end
         else
         Log('_misc_UpdateMiniMapPrimitives this seems to be replay with 8 players max');
      end;
      if (gPlayer[myPlInd].bgeology) then
      begin
         if (GetCountOfPlayers>=gc_playerind_env) then
         begin
            var envPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
            if (envPlHnd<>0) then
            begin
               for goInd:=0 to GetPlayerGameObjectsCountByHandle(envPlHnd)-1 do
               begin
                  goHnd := GetGameObjectHandleByIndex(goInd, envPlHnd);
                  if (GetGameObjectStringPropertyTag(goHnd)=gc_properties_stringtag_mines) then
                  begin
                     var primHnd : Integer = CreateGUIMiniMapPrimitive('cross1x1');
                     SetGUIMiniMapPrimitivePosition(primHnd, GetGameObjectPositionXByHandle(goHnd), GetGameObjectPositionZByHandle(goHnd));
                  end;
               end;
            end;
         end;
      end;
      if (gbool_peacemode) and (GetCountOfPlayers>=gc_playerind_misc) then
      begin
         var miscPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
         if (miscPlHnd<>0) then
         begin
            var goInd : Integer;
            for goInd:=0 to GetPlayerGameObjectsCountByHandle(miscPlHnd)-1 do
            begin
               var goHnd : Integer = GetGameObjectHandleByIndex(goInd, miscPlHnd);
               if (GetGameObjectStringPropertyTag(goHnd)=gc_properties_stringtag_ptborder) then
               begin
                  var primHnd : Integer = CreateGUIMiniMapPrimitive('ptborder');
                  if (primHnd<>0) then
                  SetGUIMiniMapPrimitivePosition(primHnd, GetGameObjectPositionXByHandle(goHnd), GetGameObjectPositionZByHandle(goHnd));
               end;
            end;
         end;
      end;
   end;
end;

// _misc_UnitUpdateMiniMapPrimitive
//
procedure _misc_UnitUpdateMiniMapPrimitive(goHnd : Integer; bSetAsSelected : Boolean);
begin
   if (GetGameObjectVisibleByHandle(goHnd)) then
   begin
      var primname : String;
      var bbuilding : Boolean;
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) then
      bbuilding := gObjProp[TObj(pobj).cid][TObj(pobj).id].bbuilding;
      if (bbuilding) then
      begin
         if (bSetAsSelected) then
         primname := 'pl3x3.sel'
         else
         primname := 'pl3x3.'+IntToStr(gMap.players[TObj(pobj).pl].color);
      end
      else
      begin
         if (bSetAsSelected) then
         primname := 'pl2x2.sel'
         else
         primname := 'pl2x2.'+IntToStr(gMap.players[TObj(pobj).pl].color);
      end;
      var behHnd : Integer = GetBehaviourByKey(goHnd, 'prim');
      if (behHnd=0) then
      behHnd := _misc_UnitCreateMiniMapPrimitive(goHnd, bSetAsSelected);
      SetBehaviourStringProperty(behHnd, 'GetOrCreatePrimitive', primname);
      SetBehaviourStringProperty(behHnd, 'MMPrimitiveItemName', primname);
   end
   else
   begin
      var behHnd : Integer = GetBehaviourByKey(goHnd, 'prim');
      if (behHnd<>0) then
      _misc_UnitRemoveMiniMapPrimitive(goHnd);
   end;
end;

// _misc_GetOrCreateGroupByName
//
function _misc_GetOrCreateGroupByName(plHnd: Integer; const grName: String) : Integer;
begin
   Result:=GetPlayerGrHandleByHandle(plHnd, grName);
   if Result=0 then begin
      Result:=CreateGroupByPlHandle(plHnd, grName);
      SetGroupNoRebuildCmdsByHandle(Result, true);
   end;
end;

// _misc_GetOrCreateSelectionGroup
//
function _misc_GetOrCreateSelectionGroup() : Integer;
begin
   var plhnd   : Integer=GetPlayerHandleInterfaceIO;
   var selgrhnd: Integer=GetPlayerGrHandleByHandle(plhnd, gc_group_selected);
   if selgrhnd=0 then
   selgrhnd:=_misc_GetOrCreateGroupByName(plhnd, gc_group_selected)
   else GroupClearGameObjectsByHandle(selgrhnd);
   Result := selgrhnd;
end;

// _misc_SaveScanGrid
//
procedure _misc_SaveScanGrid(const sfilename : String);
begin
   var pScanGrid : Integer = _parser_ParserTemporary(True);
   StateMachineGlobalVariablesSaveToParser(pScanGrid, 'gScanGrid', False, False);
   if (sfilename<>'') then
   ParserSaveToFileByHandle(pScanGrid, '.\log\'+sfilename)
   else
   ParserSaveToFileByHandle(pScanGrid, '.\log\gScanGrid');
end;

// _misc_SaveResGrid
//
procedure _misc_SaveResGrid(const sfilename : String);
begin
   var pResGrid : Integer = _parser_ParserTemporary(True);
   StateMachineGlobalVariablesSaveToParser(pResGrid, 'gResGrid', False, False);
   if (sfilename<>'') then
   ParserSaveToFileByHandle(pResGrid, '.\log\'+sfilename)
   else
   ParserSaveToFileByHandle(pResGrid, '.\log\gResGrid');
end;

// _misc_GetUnitOrderTypeByIndex
//
procedure _misc_GetUnitOrderTypeByIndex(ind : Integer; var sOrder : String);
begin
   case ind of
      gc_obj_order_type_none: sOrder := 'none';
      gc_obj_order_type_move: sOrder := 'move';
      gc_obj_order_type_attackobj: sOrder := 'attackobj';
      gc_obj_order_type_gainres: sOrder := 'gainres';
      gc_obj_order_type_produce: sOrder := 'produce';
      gc_obj_order_type_patrol: sOrder := 'patrol';
      gc_obj_order_type_attackpoint: sOrder := 'attackpoint';
      gc_obj_order_type_continueattackpoint: sOrder := 'continueattackpoint';
      gc_obj_order_type_performupgrade: sOrder := 'performupgrade';
      gc_obj_order_type_fishing: sOrder := 'fishing';
      gc_obj_order_type_creategates: sOrder := 'creategates';
      gc_obj_order_type_buildwallcontinue: sOrder := 'buildwallcontinue';
      gc_obj_order_type_buildwall: sOrder := 'buildwall';
      gc_obj_order_type_gotomine: sOrder := 'gotomine';
      gc_obj_order_type_gototransport: sOrder := 'gototransport';
      gc_obj_order_type_leavetransport: sOrder := 'leavetransport';
      gc_obj_order_type_leavebuilding: sOrder := 'leavebuilding';
      gc_obj_order_type_build: sOrder := 'build';
      gc_obj_order_type_guard: sOrder := 'guard';
      gc_obj_order_type_repair: sOrder := 'repair';
      gc_obj_order_type_exitunits: sOrder := 'exit units';
   end;
end;

// _misc_OrderToLog
//
procedure _misc_OrderToLog(ord : TOrder);
begin
   var sOrder : String;
   _misc_GetUnitOrderTypeByIndex(ord.iType, sOrder);
   Log('order.itype = ' + sOrder);
   Log('order.bexecute = ' + BoolToStr(ord.bexecute));
   Log('order.bremove = ' + BoolToStr(ord.bremove));
   Log('order.static = ' + BoolToStr(ord.static));
   Log('order.info.trg = ' + FloatToStr(ord.info.trg));
   Log('order.info.x = ' + FloatToStr(ord.info.x));
   Log('order.info.y = ' + FloatToStr(ord.info.y));
   Log('order.info.dx = ' + FloatToStr(ord.info.dx));
   Log('order.info.dy = ' + FloatToStr(ord.info.dy));
   Log('order.info.dir = ' + FloatToStr(ord.info.dir));
   Log('order.info.z = ' + FloatToStr(ord.info.z));
   Log('order.info.upgradesuid = ' + IntToStr(ord.info.upgradeid));
   Log('order.info.produceid = ' + IntToStr(ord.info.produceid));
   Log('order.info.restype = ' + IntToStr(ord.info.restype));
   Log('order.info.amount = ' + IntToStr(ord.info.amount));
   Log('order.info.progress = ' + FloatToStr(ord.info.progress));
end;

// _misc_GetSquadTypeByIndex
//
procedure _misc_GetSquadTypeByIndex(ind : Integer; var sType : String);
begin
   case ind of
      gc_ai_armytype_none: sType := 'none';
      gc_ai_armytype_lightinfantry: sType := 'lightinfantry';
      gc_ai_armytype_shootinfantry: sType := 'shootinfantry';
      gc_ai_armytype_fasthorse: sType := 'fasthorse';
      gc_ai_armytype_hardhorse: sType := 'hardhorse';
      gc_ai_armytype_horseshooter: sType := 'horseshooter';
      gc_ai_armytype_cannon: sType := 'cannon';
      gc_ai_armytype_grenadier: sType := 'grenadier';
      gc_ai_armytype_peasant: sType := 'peasant';
      gc_ai_armytype_weakunit: sType := 'weakunit';
      gc_ai_armytype_supermortar: sType := 'supermortar';
      gc_ai_armytype_mortar: sType := 'mortar';
      gc_ai_armytype_archerinfantry: sType := 'archerinfantry';
      gc_ai_armytype_galley: sType := 'galley';
      gc_ai_armytype_frigate: sType := 'frigate';
      gc_ai_armytype_battleship: sType := 'battleship';
      gc_ai_armytype_transport: sType := 'transport';
   end;
end;

// _misc_GetArmyOrderTypeByIndex
//
procedure _misc_GetArmyOrderTypeByIndex(val : Integer; var sType : String);
begin
   case val of
      gc_ai_armyorder_none: sType := 'none';
      gc_ai_armyorder_makebattle: sType := 'make battle';
      gc_ai_armyorder_bitva: sType := 'bitva';
      gc_ai_armyorder_buildmine: sType := 'buildmine';
      gc_ai_armyorder_sabotage: sType := 'sabotage';
      gc_ai_armyorder_agressor: sType := 'agressor';
      gc_ai_armyorder_makewaterbattle: sType := 'make water battle';
      gc_ai_armyorder_transport: sType := 'transport';
      gc_ai_armyorder_attackwall: sType := 'attack wall';
      else
      sType := '';
   end;
end;

// _misc_GetArmySpecNameByValue
//
procedure _misc_GetArmySpecNameByValue(val : Integer; var sType : String);
begin
   case val of
      gc_ai_armyspec_default: sType := 'default';
      gc_ai_armyspec_grenadier: sType := 'grenadier';
      gc_ai_armyspec_sabotage: sType := 'sabotage';
      gc_ai_armyspec_agressor: sType := 'agressor';
      gc_ai_armyspec_squad: sType := 'squad';
      gc_ai_armyspec_galley: sType := 'galley';
      gc_ai_armyspec_frigate: sType := 'frigate';
      gc_ai_armyspec_battleship: sType := 'battleship';
      gc_ai_armyspec_transport: sType := 'transport';
      else
      sType := '';
   end;
end;

// _misc_ArmyToLog
//
procedure _misc_ArmyToLog(pArmy : Pointer);
begin
   if pArmy <> nil then
   begin
      var ind : Integer = TArmy(pArmy).fIndex;
      var count : Integer = TArmy(pArmy).fSquadList.GetCount;
      var sSpec : String;
      _misc_GetArmySpecNameByValue(TArmy(pArmy).fSpec, sSpec);
      var sOrd : String;
      _misc_GetArmyOrderTypeByIndex(TArmy(pArmy).fOrder.iType, sOrd);
      
      var trgUID : Integer = TArmy(pArmy).fOrder.targetUID;
      
      var moveCount : Integer;
      var i : Integer;
      for i := TArmy(pArmy).fSquadList.GetCount-1 downto 0 do
      begin
         var pSquad : Pointer = TArmy(pArmy).fSquadList.GetPointer(i);
         moveCount := moveCount + TSquad(pSquad).fMoveCount;
      end;
      
      Log('army[' + IntToStr(ind) + '].SquadCount = ' + IntToStr(count));
      Log('army[' + IntToStr(ind) + '].Spec = ' + sSpec);
      Log('army[' + IntToStr(ind) + '].TopZone = ' + IntToStr(TArmy(pArmy).fTopZone));
      Log('army[' + IntToStr(ind) + '].x = ' + FloatToStr(TArmy(pArmy).fX));
      Log('army[' + IntToStr(ind) + '].z = ' + FloatToStr(TArmy(pArmy).fZ));
      Log('army[' + IntToStr(ind) + '].curX = ' + FloatToStr(TArmy(pArmy).fCurX));
      Log('army[' + IntToStr(ind) + '].curZ = ' + FloatToStr(TArmy(pArmy).fCurZ));
      Log('army[' + IntToStr(ind) + '].SpecialOrder = ' + BoolToStr(TArmy(pArmy).fSpecialOrder));
      Log('army[' + IntToStr(ind) + '].Force = ' + FloatToStr(TArmy(pArmy).fForce));
      Log('army[' + IntToStr(ind) + '].LastBattleTime = ' + FloatToStr(TArmy(pArmy).fLastBattleTime));
      Log('army[' + IntToStr(ind) + '].MoveCount = ' + IntToStr(moveCount));
      Log('army[' + IntToStr(ind) + '].Active = ' + BoolToStr(TArmy(pArmy).fActive));
      Log('army[' + IntToStr(ind) + '].fOrder.type = ' + sOrd);
      Log('army[' + IntToStr(ind) + '].fOrder.trgHnd = ' + IntToStr(GetGameObjectHandleByUniqueId(TArmy(pArmy).fOrder.targetUID)));
      Log('army[' + IntToStr(ind) + '].fOrder.trgUID = ' + IntToStr(trgUID));
      Log('army[' + IntToStr(ind) + '].fOrder.tag = ' + IntToStr(TArmy(pArmy).fOrder.tag));
      Log('army[' + IntToStr(ind) + '].fOrder.tag2 = ' + IntToStr(TArmy(pArmy).fOrder.tag2));
   end;
end;

// _misc_GetStateTagNameByValue
//
procedure _misc_GetStateTagNameByValue(tag : Integer; var sTag : String);
begin
   case tag of
      gc_statetag_essential_none: sTag := 'gc_statetag_essential_none';
      gc_statetag_essential_birth: sTag := 'gc_statetag_essential_birth';
      gc_statetag_essential_death: sTag := 'gc_statetag_essential_death';
      gc_statetag_move_idle: sTag := 'gc_statetag_move_idle';
      gc_statetag_move_walk: sTag := 'gc_statetag_move_walk';
      gc_statetag_move_turn: sTag := 'gc_statetag_move_turn';
      gc_statetag_action_none: sTag := 'gc_statetag_action_none';
      gc_statetag_action_attack: sTag := 'gc_statetag_action_attack';
      gc_statetag_action_build: sTag := 'gc_statetag_action_build';
      gc_statetag_action_extract: sTag := 'gc_statetag_action_extract';
      gc_statetag_execute_none: sTag := 'gc_statetag_execute_none';
      gc_statetag_execute_move: sTag := 'gc_statetag_execute_move';
      gc_statetag_weapon_none: sTag := 'gc_statetag_weapon_none';
      gc_statetag_weapon_0: sTag := 'gc_statetag_weapon_0';
      gc_statetag_weapon_1: sTag := 'gc_statetag_weapon_1';
      gc_statetag_weapon_2: sTag := 'gc_statetag_weapon_2';
      gc_statetag_resource_none: sTag := 'gc_statetag_resource_none';
      gc_statetag_resource_food: sTag := 'gc_statetag_resource_food';
      gc_statetag_resource_wood: sTag := 'gc_statetag_resource_wood';
      gc_statetag_resource_stone: sTag := 'gc_statetag_resource_stone';
      gc_statetag_visual_none: sTag := 'gc_statetag_visual_none';
      gc_statetag_visual_stage_0: sTag := 'gc_statetag_visual_stage_0';
      gc_statetag_visual_stage_1: sTag := 'gc_statetag_visual_stage_1';
      gc_statetag_visual_stage_2: sTag := 'gc_statetag_visual_stage_2';
      gc_statetag_visual_stage_3: sTag := 'gc_statetag_visual_stage_3';
      gc_statetag_visual_hide: sTag := 'gc_statetag_visual_hide';
      gc_statetag_sync_endpoint: sTag := 'gc_statetag_sync_endpoint'
      else sTag := IntToStr(tag);
   end;
end;

// _misc_GetUsageNameByValue
//
procedure _misc_GetUsageNameByValue(usage : Integer; var sUsage : String);
begin
   case usage of
      gc_obj_usage_none: sUsage := 'gc_obj_usage_none';
      gc_obj_usage_mill: sUsage := 'gc_obj_usage_mill';
      gc_obj_usage_farm: sUsage := 'gc_obj_usage_farm';
      gc_obj_usage_center: sUsage := 'gc_obj_usage_center';
      gc_obj_usage_storage: sUsage := 'gc_obj_usage_storage';
      gc_obj_usage_tower: sUsage := 'gc_obj_usage_tower';
      gc_obj_usage_field: sUsage := 'gc_obj_usage_field';
      gc_obj_usage_mine: sUsage := 'gc_obj_usage_mine';
      gc_obj_usage_fasthorse: sUsage := 'gc_obj_usage_fasthorse';
      gc_obj_usage_mortar: sUsage := 'gc_obj_usage_mortar';
      gc_obj_usage_cannon: sUsage := 'gc_obj_usage_cannon';
      gc_obj_usage_grenadier: sUsage := 'gc_obj_usage_grenadier';
      gc_obj_usage_hardwall: sUsage := 'gc_obj_usage_hardwall';
      gc_obj_usage_weakwall: sUsage := 'gc_obj_usage_weakwall';
      gc_obj_usage_battleship: sUsage := 'gc_obj_usage_battleship';
      gc_obj_usage_weak: sUsage := 'gc_obj_usage_weak';
      gc_obj_usage_fisher: sUsage := 'gc_obj_usage_fisher';
      gc_obj_usage_artdepo: sUsage := 'gc_obj_usage_artdepo';
      gc_obj_usage_supermortar: sUsage := 'gc_obj_usage_supermortar';
      gc_obj_usage_port: sUsage := 'gc_obj_usage_port';
      gc_obj_usage_lightinfantry: sUsage := 'gc_obj_usage_lightinfantry';
      gc_obj_usage_shooter: sUsage := 'gc_obj_usage_shooter';
      gc_obj_usage_hardhorse: sUsage := 'gc_obj_usage_hardhorse';
      gc_obj_usage_peasant: sUsage := 'gc_obj_usage_peasant';
      gc_obj_usage_horseshooter: sUsage := 'gc_obj_usage_horseshooter';
      gc_obj_usage_frigate: sUsage := 'gc_obj_usage_frigate';
      gc_obj_usage_galley: sUsage := 'gc_obj_usage_galley';
      gc_obj_usage_yacht: sUsage := 'gc_obj_usage_yacht';
      gc_obj_usage_xebec: sUsage := 'gc_obj_usage_xebec';
      gc_obj_usage_transport: sUsage := 'gc_obj_usage_transport';
      gc_obj_usage_archer: sUsage := 'gc_obj_usage_archer';
      gc_obj_usage_mcannon: sUsage := 'gc_obj_usage_mcannon';
   end;
end;

// _misc_UnitStatesTagToLog
//
procedure _misc_UnitStatesTagToLog(goHnd : Integer);
begin
   var i : Integer;
   var s : String;
   var tag : Integer = GetGameObjectStatesTagByHandle(goHnd);
   Log(IntToStr(goHnd) + ' tags:');
   
   for i := 0 to 31 do
   if (tag and (1 shl i)) > 0 then
   begin
      _misc_GetStateTagNameByValue(1 shl i, s);
      Log(s);
   end;
end;

// _misc_GetDirAngleToXVector
//
function _misc_GetDirAngleToXVector(dirX, dirZ : Float) : Float;
begin
   if VectorLength(dirX, 0, dirZ) > 0 then
   begin
      Result := VectorAngle(1, 0, 0, dirX, 0, dirZ);
      var x, y, z : Float;
      VectorCross(1, 0, 0, dirX, 0, dirZ, x, y, z);
      if y < 0 then
      Result := -Result;
   end
   else
   Result := 0;
end;

// _misc_GetAngleToXVector
//
function _misc_GetAngleToXVector(stX, stY, endX, endY : Float) : Float;
begin
   var dirX : Float = endX-stX;
   var dirZ : Float = endY-stY;
   Result := _misc_GetDirAngleToXVector(dirX, dirZ);
end;

// _misc_GetAngleToXVectorInvert
//
function _misc_GetAngleToXVectorInvert(stX, stY, endX, endY : Float) : Float;
begin
   var dirX : Float = endX-stX;
   var dirZ : Float = endY-stY;
   if VectorLength(dirX, 0, dirZ) > 0 then
   begin
      Result := VectorAngle(-1, 0, 0, dirX, 0, dirZ);
      var x, y, z : Float;
      VectorCross(-1, 0, 0, dirX, 0, dirZ, x, y, z);
      if y > 0 then
      Result := Abs(Result-180)+180;
   end;
end;

// _misc_GetAngleToXVectorDirectInvert
//
function _misc_GetAngleToXVectorDirectInvert(dirX, dirZ : Float) : Float;
begin
   if VectorLength(dirX, 0, dirZ) > 0 then
   begin
      Result := VectorAngle(-1, 0, 0, dirX, 0, dirZ);
      var x, y, z : Float;
      VectorCross(-1, 0, 0, dirX, 0, dirZ, x, y, z);
      if y > 0 then
      Result := Abs(Result-180)+180;
   end;
end;

// _misc_DebugLogUnitStateTag
//
procedure _misc_DebugLogUnitStateTag(goHnd : Integer; bJustSwitched : Boolean);
begin
   var oldState : Integer = GetGameObjectPrevStatesTagByHandle(goHnd);
   var newState : Integer = GetGameObjectStatesTagByHandle(goHnd);
   
   var iOldEssential : Integer = oldState and gc_statetag_essential;
   var iEssential : Integer = newState and gc_statetag_essential;
   var iOldResource : Integer = oldState and gc_statetag_resource;
   var iResource : Integer = newState and gc_statetag_resource;
   var iOldExecute : Integer = oldState and gc_statetag_execute;
   var iExecute : Integer = newState and gc_statetag_execute;
   var iOldMove : Integer = oldState and gc_statetag_move;
   var iMove : Integer = newState and gc_statetag_move;
   var iOldWeapon : Integer = oldState and gc_statetag_weapon;
   var iWeapon : Integer = newState and gc_statetag_weapon;
   var iOldAction : Integer = oldState and gc_statetag_action;
   var iAction : Integer = newState and gc_statetag_action;
   var switchEssential : Integer;
   var switchResource : Integer;
   var switchExecute : Integer;
   var switchMove : Integer;
   var switchWeapon : Integer;
   var switchAction : Integer;
   if (iOldEssential<>iEssential) then switchEssential := iEssential;
   if (iOldResource<>iResource) then switchResource := iResource;
   if (iOldExecute<>iExecute) then switchExecute := iExecute;
   if (iOldMove<>iMove) then switchMove := iMove;
   if (iOldWeapon<>iWeapon) then switchWeapon := iWeapon;
   if (iOldAction<>iAction) then switchAction := iAction;
   
   var s, val : String;
   if (bJustSwitched) then
   begin
      if (switchEssential<>0) then
      begin
         case switchEssential of
            gc_statetag_essential_none : val := 'none';
            gc_statetag_essential_birth : val := 'birth';
            gc_statetag_essential_death : val := 'death';
         end;
         s := s+'switchEssential='; if val<>'' then s:=s+val else s:=s+IntToStr(switchEssential); s := s+' '; val := '';
      end;
      if (switchResource<>0) then
      begin
         case switchResource of
            gc_statetag_resource_none : val := 'none';
            gc_statetag_resource_food : val := 'food';
            gc_statetag_resource_wood : val := 'wood';
            gc_statetag_resource_stone : val := 'stone';
         end;
         s := s+'switchResource='; if val<>'' then s:=s+val else s:=s+IntToStr(switchResource); s := s+' '; val := '';
      end;
      if (switchExecute<>0) then
      begin
         case switchExecute of
            gc_statetag_execute_none : val := 'none';
            gc_statetag_execute_move : val := 'move';
         end;
         s := s+'switchExecute='; if val<>'' then s:=s+val else s:=s+IntToStr(switchExecute); s := s+' '; val := '';
      end;
      if (switchMove<>0) then
      begin
         case switchMove of
            gc_statetag_move_idle : val := 'idle';
            gc_statetag_move_walk : val := 'walk';
            gc_statetag_move_turn : val := 'turn';
         end;
         s := s+'switchMove='; if val<>'' then s:=s+val else s:=s+IntToStr(switchMove); s := s+' '; val := '';
      end;
      if (switchWeapon<>0) then
      begin
         case switchWeapon of
            gc_statetag_weapon_none : val := 'none';
            gc_statetag_weapon_0 : val := 'weap0';
            gc_statetag_weapon_1 : val := 'weap1';
            gc_statetag_weapon_2 : val := 'weap2';
         end;
         s := s+'switchWeapon='; if val<>'' then s:=s+val else s:=s+IntToStr(switchWeapon); s := s+' '; val := '';
      end;
      if (switchAction<>0) then
      begin
         case switchAction of
            gc_statetag_action_none : val := 'none';
            gc_statetag_action_attack : val := 'attack';
            gc_statetag_action_build : val := 'build';
            gc_statetag_action_extract : val := 'extract';
         end;
         s := s+'switchAction='; if val<>'' then s:=s+val else s:=s+IntToStr(switchAction); s := s+' '; val := '';
      end;
   end
   else
   begin
      if (iEssential<>0) then
      begin
         case iEssential of
            gc_statetag_essential_none : val := 'none';
            gc_statetag_essential_birth : val := 'birth';
            gc_statetag_essential_death : val := 'death';
         end;
         s := s+'iEssential='; if val<>'' then s:=s+val else s:=s+IntToStr(iEssential); s := s+' '; val := '';
      end;
      if (iResource<>0) then
      begin
         case iResource of
            gc_statetag_resource_none : val := 'none';
            gc_statetag_resource_food : val := 'food';
            gc_statetag_resource_wood : val := 'wood';
            gc_statetag_resource_stone : val := 'stone';
         end;
         s := s+'iResource='; if val<>'' then s:=s+val else s:=s+IntToStr(iResource); s := s+' '; val := '';
      end;
      if (iExecute<>0) then
      begin
         case iExecute of
            gc_statetag_execute_none : val := 'none';
            gc_statetag_execute_move : val := 'move';
         end;
         s := s+'iExecute='; if val<>'' then s:=s+val else s:=s+IntToStr(iExecute); s := s+' '; val := '';
      end;
      if (iMove<>0) then
      begin
         case iMove of
            gc_statetag_move_idle : val := 'idle';
            gc_statetag_move_walk : val := 'walk';
            gc_statetag_move_turn : val := 'turn';
         end;
         s := s+'iMove='; if val<>'' then s:=s+val else s:=s+IntToStr(iMove); s := s+' '; val := '';
      end;
      if (iWeapon<>0) then
      begin
         case iWeapon of
            gc_statetag_weapon_none : val := 'none';
            gc_statetag_weapon_0 : val := 'weap0';
            gc_statetag_weapon_1 : val := 'weap1';
            gc_statetag_weapon_2 : val := 'weap2';
         end;
         s := s+'iWeapon='; if val<>'' then s:=s+val else s:=s+IntToStr(iWeapon); s := s+' '; val := '';
      end;
      if (iAction<>0) then
      begin
         case iAction of
            gc_statetag_action_none : val := 'none';
            gc_statetag_action_attack : val := 'attack';
            gc_statetag_action_build : val := 'build';
            gc_statetag_action_extract : val := 'extract';
         end;
         s := s+'iAction='; if val<>'' then s:=s+val else s:=s+IntToStr(iAction); s := s+' '; val := '';
      end;
   end;
   if s='' then s:='none';
   Log(s);
end;

// _misc_GetPositionOnLine3D
//
procedure _misc_GetPositionOnLine3D(x1, y1, z1, x2, y2, z2, dist : Float; var x, y, z : Float);
begin
   var dirX : Float = x2-x1;
   var dirY : Float = y2-y1;
   var dirZ : Float = z2-z1;
   var d : Float = VectorLength(dirX, dirY, dirZ);
   if d > 0 then
   begin
      dirX := dirX/d;
      dirY := dirY/d;
      dirZ := dirZ/d;
      x := x1 + dirX * dist;
      y := y1 + dirY * dist;
      z := z1 + dirZ * dist;
   end;
end;

// _misc_GetPositionOnLine
//
procedure _misc_GetPositionOnLine(x1, y1, x2, y2, dist : Float; var x, y : Float);
begin
   var dirX : Float = x2-x1;
   var dirY : Float = y2-y1;
   var d : Float = VectorLength(dirX, 0, dirY);
   if d > 0 then
   begin
      dirX := dirX/d;
      dirY := dirY/d;
      x := x1 + dirX * dist;
      y := y1 + dirY * dist;
   end
   else
   begin
      x := x1 + (1-2*RandomExt) * dist;
      y := y1 + (1-2*RandomExt) * dist;
   end;
end;

// _misc_ErrorLog
//
procedure _misc_ErrorLog(const inf: String);
begin
   if GetCurrentStateIsUsedFile then
   ErrorLog(GetCurrentStateUsedFileName+' :: '+inf)
   else ErrorLog(GetCurrentStateMachineFileName+' ::'+GetCurrentStateName+':: '+inf);
end;

// _misc_LogInt
//
procedure _misc_LogInt(const val : Integer);
begin
   Log(IntToStr(val));
end;

// _misc_LogBool
//
procedure _misc_LogBool(const val : Boolean);
begin
   Log(BoolToStr(val));
end;

// _misc_LogFloat
//
procedure _misc_LogFloat(const val : Float);
begin
   Log(FloatToStr(val));
end;

// _misc_GetFormationIndexBySID
//
function _misc_GetFormationIndexBySID(var masksid : String) : Integer;
begin
   Result := -1;
   var i : Integer;
   for i:=0 to gc_formation_maxcount-1 do
   begin
      if (gFormation[i].sid=masksid) then
      begin
         Result := i;
         break;
      end;
   end;
end;

// _misc_SortList
//
procedure _misc_SortList(var list : TIntegerList);
begin
   var i : Integer;
   var count : Integer = list.GetCount;
   
   var curInd : Integer;
   for curInd := 0 to count-1 do
   begin
      var val : Integer = list.Get(curInd);
      var minInd : Integer = 0;
      var maxInd : Integer = curInd-1;
      
      while maxInd-minInd > 0 do
      begin
         var ind : Integer = minInd + (maxInd - minInd) div 2;
         var val1 : Integer = list.Get(ind);
         if val > val1 then
         begin
            if ind < maxInd then
            minInd := ind+1
            else
            break;
         end
         else
         begin
            if ind > minInd then
            maxInd := ind-1
            else
            begin
               maxInd := minInd;
               break;
            end;
         end;
      end;
      
      if maxInd >= 0 then
      begin
         var val1 : Integer = list.Get(maxInd);
         if val > val1 then
         maxInd := maxInd+1;
         
         if maxInd < curInd then
         for i := curInd downto maxInd+1 do
         begin
            var val : Integer = list.Get(i);
            list.Set(list.Get(i-1), i);
            list.Set(val, i-1);
         end;
      end;
   end;
end;

// _misc_SortListByTagFloat
//
procedure _misc_SortListByTagFloat(var list : TIntegerList; minIndex, maxIndex : Integer);
begin
   var i : Integer;
   if maxIndex >= list.GetCount then
   maxIndex := list.GetCount-1;
   if minIndex < 0 then
   minIndex := 0;
   
   var curInd : Integer;
   for curInd := minIndex to maxIndex do
   begin
      var goHnd : Integer = list.Get(curInd);
      var tag : Float = GetGameObjectTagFloatByHandle(goHnd);
      var minInd : Integer = minIndex;
      var maxInd : Integer = curInd-1;
      
      while maxInd-minInd > 0 do
      begin
         var ind : Integer = minInd + (maxInd - minInd) div 2;
         var goHnd1 : Integer = list.Get(ind);
         var tag1 : Float = GetGameObjectTagFloatByHandle(goHnd1);
         if tag > tag1 then
         begin
            if ind < maxInd then
            minInd := ind+1
            else
            break;
         end
         else
         begin
            if ind > minInd then
            maxInd := ind-1
            else
            begin
               maxInd := minInd;
               break;
            end;
         end;
      end;
      
      if maxInd >= minIndex then
      begin
         var goHnd1 : Integer = list.Get(maxInd);
         var tag1 : Float = GetGameObjectTagFloatByHandle(goHnd1);
         if tag > tag1 then
         maxInd := maxInd+1;
         
         if maxInd < curInd then
         for i := curInd downto maxInd+1 do
         begin
            var val : Integer = list.Get(i);
            list.Set(list.Get(i-1), i);
            list.Set(val, i-1);
         end;
      end;
   end;
end;

// _misc_SortListByDirection
//
procedure _misc_SortListByDirection(var list : TIntegerList; minIndex, maxIndex : Integer; posX, posZ, dirX, dirZ : Float);
begin
   var i : Integer;
   if maxIndex >= list.GetCount then
   maxIndex := list.GetCount-1;
   if minIndex < 0 then
   minIndex := 0;
   
   for i := minIndex to maxIndex do
   begin
      var goHnd : Integer = list.Get(i);
      var goX : Float = GetGameObjectPositionXByHandle(goHnd);
      var goZ : Float = GetGameObjectPositionZByHandle(goHnd);
      var dx : Float = goX - posX;
      var dz : Float = goZ - posZ;
      var angle : Float = VectorAngle(dirX, 0, dirZ, dx, 0, dz);
      var dist : Float = Cos(angle*pi/180) * VectorDistance(goX, 0, goZ, posX, 0, posZ);
      SetGameObjectTagFloatByHandle(goHnd, dist);
   end;
   
   _misc_SortListByTagFloat(list, minIndex, maxIndex);
end;

// _misc_CopyIntListRange
//
procedure _misc_CopyIntListRange(var dest, src : TIntegerList; startIndex : Integer; count : Integer; clearSource : boolean);
begin
   var i : Integer;
   var icount : Integer;
   if ((startIndex+count)>src.GetCount) then
   begin
      ErrorLog('_misc_CopyIntListRange : out of range');
      icount := src.GetCount-startIndex;
   end
   else
   icount := count;
   for i := startIndex to icount-1 do
   dest.add(src.get(i));
   if (clearSource=True) then
   src.DeleteItems(startIndex, icount);
end;

// _misc_GetGUIShowEventStateNames
//
procedure _misc_GetGUIShowEventStateNames(const statename : String; var elmname, showstate, eventstate : String);
begin
   if StrExists(statename, 'Show') then
   elmname := StrReplace(statename, 'Show', '')
   else
   if StrExists(statename, 'Event') then
   elmname := StrReplace(statename, 'Event', '')
   else
   ErrorLog('_misc_GetGUIShowEventStateNames unknown elmname');
   showstate := 'Show'+elmname;
   eventstate := 'Event'+elmname;
end;

// _misc_PosToIdleGridIndices
//
function _misc_PosToIdleGridIndices(const x, y : Float; var gridX, gridY : Integer) : Boolean;
begin
   var posX : Float = x + (GetMapWidth div 2);
   var posZ : Float = y + (GetMapHeight div 2);
   gridX := Floor(posX/gc_idlegrid_size);
   gridY := Floor(posZ/gc_idlegrid_size);
   Result := (gridX>=0) and (gridY>=0) and (gridX<gc_idlegrid_countx) and (gridY<gc_idlegrid_county);
end;

// _misc_PosToResGridIndices
//
function _misc_PosToResGridIndices(const x, y : Float; var gridX, gridY : Integer) : Boolean;
begin
   var posX : Float = x + (GetMapWidth div 2);
   var posZ : Float = y + (GetMapHeight div 2);
   gridX := Floor(posX/gc_resgrid_size);
   gridY := Floor(posZ/gc_resgrid_size);
   Result := (gridX>=0) and (gridY>=0) and (gridX<gc_resgrid_countx) and (gridY<gc_resgrid_county);
end;

// _misc_PosToScanGridIndices
//
function _misc_PosToScanGridIndices(const x, y : Float; var gridX, gridY : Integer) : Boolean;
begin
   gridX := Floor((x+(GetMapWidth div 2))/gc_scangrid_size) ;
   gridY := Floor((y+(GetMapHeight div 2))/gc_scangrid_size);
   Result := (gridX>=0) and (gridY>=0) and (gridX<gc_scangrid_countx) and (gridY<gc_scangrid_county);
end;

// _misc_PosToSoundGridIndices
//
function _misc_PosToSoundGridIndices(const x, y : Float; var gridX, gridY : Integer) : Boolean;
begin
   gridX := Floor((x+(GetMapWidth div 2))/gc_soundgrid_size) ;
   gridY := Floor((y+(GetMapHeight div 2))/gc_soundgrid_size);
   Result := (gridX>=0) and (gridY>=0) and (gridX<gc_soundgrid_countx) and (gridY<gc_soundgrid_county);
end;

// _misc_SoundGridIndicesToPos
//
function _misc_SoundGridIndicesToPos(const gridX, gridY : Float; var x, y : Float) : Boolean;
begin
   var hw : Integer = GetMapWidth div 2;
   var hh : Integer = GetMapHeight div 2;
   x := (gridX+0.5) * gc_soundgrid_size - hw;
   y := (gridY+0.5) * gc_soundgrid_size - hh;
   Result := IsInRange(x, -hw, hw) and IsInRange(y, -hh, hh);
end;

// _misc_PosToScanGridPos
//
function _misc_PosToScanGridPos(const x, y : Float; var gridX, gridY : Float) : Boolean;
begin
   gridX := (x + GetMapWidth div 2)/gc_scangrid_size;
   gridY := (y + GetMapHeight div 2)/gc_scangrid_size;
   Result := (gridX>=0) and (gridY>=0) and (gridX<gc_scangrid_countx) and (gridY<gc_scangrid_county);
end;

// _misc_ScanGridIndicesToPos
//
function _misc_ScanGridIndicesToPos(const gridX, gridY : Float; var x, y : Float) : Boolean;
begin
   var hw : Integer = GetMapWidth div 2;
   var hh : Integer = GetMapHeight div 2;
   x := (gridX+0.5) * gc_scangrid_size - hw;
   y := (gridY+0.5) * gc_scangrid_size - hh;
   Result := IsInRange(x, -hw, hw) and IsInRange(y, -hh, hh);
end;

// _misc_PosToColGridIndices
//
procedure _misc_PosToColGridIndices(x, y : Float; var i, j : Integer);
begin
   i := Floor(2*x) + GetMapWidth;
   j := Floor(2*y) + GetMapHeight;
end;

// _misc_ColGridIndicesToPos
//
procedure _misc_ColGridIndicesToPos(i, j : Integer; var x, y : Float);
begin
   x := (i - GetMapWidth)/2;
   y := (j - GetMapHeight)/2;
end;

// _misc_GetUnitsListCentralPosExt
//
function _misc_GetUnitsListCentralPosExt(var list : TIntegerList; var posx, posz : Float; essential : Integer) : Boolean;
begin
   Result := False;
   var tmpcount : Integer;
   var count : Integer = list.GetCount;
   var i : Integer;
   for i:=count-1 downto 0 do
   begin
      var goHnd : Integer = list.Get(i);
      if (essential=0) or ((GetGameObjectStatesTagByHandle(goHnd) and essential)<>0) then
      begin
         posx := posx+GetGameObjectPositionXByHandle(goHnd);
         posz := posz+GetGameObjectPositionZByHandle(goHnd);
         tmpcount := tmpcount+1;
      end;
   end;
   if (tmpcount>0) then
   begin
      posx := posx/tmpcount;
      posz := posz/tmpcount;
      Result := True;
   end;
end;

// _misc_GetUnitsListCentralPos
//
function _misc_GetUnitsListCentralPos(var list : TIntegerList; var posx, posz : Float) : Boolean;
begin
   var count : Integer = list.GetCount;
   Result := (count>0);
   if (Result) then
   begin
      var i : Integer;
      for i:=count-1 downto 0 do
      begin
         var goHnd : Integer = list.Get(i);
         posx := posx+GetGameObjectPositionXByHandle(goHnd);
         posz := posz+GetGameObjectPositionZByHandle(goHnd);
      end;
      posx := posx/count;
      posz := posz/count;
   end;
end;

// _misc_GetUnitsListCentralPosAndDir
//
function _misc_GetUnitsListCentralPosAndDir(var list : TIntegerList; var posx, posz, dirX, dirZ : Float) : Boolean;
begin
   posX := 0;
   posZ := 0;
   dirX := 0;
   dirZ := 0;
   
   var count : Integer = list.GetCount;
   Result := (count>0);
   if (Result) then
   begin
      var i : Integer;
      for i:=count-1 downto 0 do
      begin
         var goHnd : Integer = list.Get(i);
         posx := posx + GetGameObjectPositionXByHandle(goHnd);
         posz := posz + GetGameObjectPositionZByHandle(goHnd);
         dirX := dirX + GetGameObjectTransformedVirtualDirectionXByHandle(goHnd);
         dirZ := dirZ + GetGameObjectTransformedVirtualDirectionZByHandle(goHnd);
      end;
      posx := posx/count;
      posz := posz/count;
      var dirY : Float;
      if VectorLength(dirX, 0, dirZ) > 0 then
      VectorNormalize(dirX, dirY, dirZ);
   end;
end;

// _misc_GetUnitsListEndPointCentralPos
//
function _misc_GetUnitsListEndPointCentralPos(var list : TIntegerList; var posx, posz : Float) : Boolean;
begin
   var count : Integer;
   var i, j : Integer;
   for i:=list.GetCount-1 downto 0 do
   begin
      var goHnd : Integer = list.Get(i);
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) then
      begin
         for j:=gc_obj_MaxOrderCount-1 downto 0 do
         begin
            if (TObj(pobj).orders[j].itype=gc_obj_order_type_move) then
            begin
               posx := posx+TObj(pobj).orders[j].info.x;
               posz := posz+TObj(pobj).orders[j].info.y;
               count := count+1;
               break;
            end;
            if (j=0) then
            begin
               posx := posx+GetGameObjectPositionXByHandle(goHnd);
               posz := posz+GetGameObjectPositionZByHandle(goHnd);
               count := count+1;
            end;
         end;
      end;
   end;
   Result := (count>0);
   if (Result) then
   begin
      posx := posx/count;
      posz := posz/count;
   end;
end;

// _misc_GetSquadMoveMode
//
function _misc_GetSquadMoveMode(psquad : Pointer) : Integer;
begin
   Result := gc_obj_order_move_mode_default;
   if (psquad<>nil) then
   begin
      var i, j : Integer;
      var count : Integer;
      var movemode : Float;
      for i:=TSquad(pSquad).GetCount-1 downto 0 do
      begin
         var goHnd : Integer = TSquad(pSquad).Get(i);
         var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            for j:=0 to gc_obj_MaxOrderCount-1 do
            begin
               if (TObj(pobj).orders[j].itype=gc_obj_order_type_move) then
               begin
                  movemode := movemode+TObj(pobj).orders[j].info.progress;
                  count := count+1;
                  break;
               end;
            end;
         end;
      end;
      if (count>0) then
      movemode := movemode/count;
      Result := round(movemode);
   end;
end;

// _misc_GetUnitsListMoveMode
//
function _misc_GetUnitsListMoveMode(var list : TIntegerList) : Integer;
begin
   Result := gc_obj_order_move_mode_default;
   var i, j : Integer;
   var count : Integer;
   var movemode : Float;
   for i:=list.GetCount-1 downto 0 do
   begin
      var goHnd : Integer = list.Get(i);
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) then
      begin
         for j:=0 to gc_obj_MaxOrderCount-1 do
         begin
            if (TObj(pobj).orders[j].itype=gc_obj_order_type_move) then
            begin
               movemode := movemode+TObj(pobj).orders[j].info.progress;
               count := count+1;
               break;
            end;
         end;
      end;
   end;
   if (count>0) then
   movemode := movemode/count;
   Result := round(movemode);
end;

// _misc_SetUnitsListUnitState
//
procedure _misc_SetUnitsListUnitState(var unitslist : TIntegerList; bchangestandground, bchangesearchvictim, bstandground, bsearchvictim : Boolean);
begin
   var i : Integer;
   for i:=unitslist.GetCount-1 downto 0 do
   begin
      var goHnd : Integer = unitslist.Get(i);
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      var prevsquad : Integer = -1; // small optimization
      if (pobj<>nil) then
      begin
         if (TObj(pobj).squad>=0) and (TObj(pobj).squad<>prevsquad) then
         begin
            var pSquad : Pointer = gPlayer[TObj(pobj).pl].squads.Get(TObj(pobj).squad);
            if (bchangestandground) then
            begin
               TSquad(psquad).fStandGround := bstandground;
               if (not TSquad(psquad).fStandGround) and (TSquad(psquad).fHoldMode) then
               TSquad(psquad).fHoldMode := False;
            end;
            if (bchangesearchvictim) then TSquad(psquad).fSearchVictim := bsearchvictim;
            prevsquad := TObj(pobj).squad;
         end;
         if (bchangestandground) then TObj(pobj).bstandground := bstandground;
         if (bchangesearchvictim) then TObj(pobj).bsearchenemy := bsearchvictim;
      end
      else
      if (gProfile.bDbgLogPointerNil) then
      _init_DebugPointerNil(pobj);
   end;
end;

// _misc_IsBuildingInRay
//
function _misc_IsBuildingInRay(ignoreHnd : Integer; x1, y1, z1, x2, y2, z2 : Float) : Integer;
begin
   _misc_ProfilerBegin('ray');
   Result := 0;
   var dirX : Float = x2-x1;
   var dirY : Float;
   var dirZ : Float = z2-z1;
   if VectorLength(dirX, dirY, dirZ) > 0 then
   begin
      VectorNormalize(dirX, dirY, dirZ);
      
      var dirGridX : Float = dirX * gc_scangrid_tracestep / gc_scangrid_size;
      var dirGridZ : Float = dirZ * gc_scangrid_tracestep / gc_scangrid_size;
      
      var minX : Float = _misc_MinFloat(x1, x2);
      var minZ : Float = _misc_MinFloat(z1, z2);
      var maxX : Float = x1 + x2 - minX;
      var maxZ : Float = z1 + z2 - minZ;
      var minGridX, minGridY, maxGridX, maxGridY : Integer;
      _misc_PosToScanGridIndices(minX, minZ, minGridX, minGridY);
      _misc_PosToScanGridIndices(maxX, maxZ, maxGridX, maxGridY);
      
      minGridX := Max(0, minGridX-1);
      minGridY := Max(0, minGridY-1);
      maxGridX := Min(gc_scangrid_countx-1, maxGridX+1);
      maxGridY := Min(gc_scangrid_county-1, maxGridY+1);
      
      var i, j : Integer;
      for i := minGridX to maxGridX do
      for j := minGridY to maxGridY do
      gScanGrid[i, j].fChecked := false;
      
      var iCurGridX, iCurGridY : Integer;
      _misc_PosToScanGridIndices(x1, z1, iCurGridX, iCurGridY);
      var fCurGridX : Float = iCurGridX;
      var fCurGridY : Float = iCurGridY;
      
      var dist : Float = VectorDistance(x1, 0, z1, x2, 0, z2) / gc_scangrid_size;
      var curDist : Float;
      var prevGridX, prevGridY : Integer = -1;
      var gridX, gridY : Integer;
      var tmpgridx, tmpgridY : Integer;
      var posX, posZ : Float;
      var dx, dy : Integer;
      var tmpy1, tmpy2 : Float; // if y<0 GetGameObjectRayCastAABBByHandle always return wrong result
      if y1<0.05 then
      tmpy1 := 0.05
      else
      tmpy1 := y1;
      if y2<0.05 then
      tmpy2 := 0.05
      else
      tmpy2 := y2;
      
      var enemyplmask : Integer;
      if (ignoreHnd<>0) then
      begin
         var pignoreobj : Pointer = _misc_GetObjectArgData(ignoreHnd, gc_argunit_obj);
         if (pignoreobj<>nil) then
         enemyplmask := gPlayer[TObj(pignoreobj).pl].enemyplmask
         else
         enemyplmask := $ffff;
      end
      else
      enemyplmask := $ffff;
      
      var mmask : Integer = (1 shl gc_obj_material_building) or (1 shl gc_obj_material_wood) or (1 shl gc_obj_material_stone) or (1 shl gc_obj_material_woodwall);
      while [MAIN]curDist < dist do
      begin
         gridX := Floor(fCurGridX);
         gridY := Floor(fCurGridY);
         
         if (gridX <> prevGridX) or (gridY <> prevGridY) then
         begin
            for dx := -1 to 1 do
            for dy := -1 to 1 do
            begin
               tmpgridx := gridX+dx;
               tmpgridy := gridY+dy;
               if (tmpgridx >= 0) and (tmpgridx < gc_scangrid_countx) and (tmpgridy >= 0) and (tmpgridy < gc_scangrid_county) and
               (not gScanGrid[tmpgridx, tmpgridy].fChecked) then
               begin
                  gScanGrid[tmpgridx, tmpgridy].fChecked := true;
                  if ((gScanGrid[tmpgridx, tmpgridy].fMask and mmask)<>0) then
                  for i := gScanGrid[tmpgridx, tmpgridy].GetCount-1 downto 0 do
                  begin
                     var goHnd : Integer = gScanGrid[tmpgridx, tmpgridy].Get(i);
                     if (goHnd<>ignoreHnd) then
                     begin
                        var pObj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
                        if pObj <> nil then
                        begin
                           if (TBaseObj(pObj).baseid=gc_baseid_obj) then
                           begin
                              var cid : Integer = TObj(pObj).cid;
                              var id : Integer = TObj(pObj).id;
                              if (gObjProp[cid, id].bBuilding) or (gObjProp[cid, id].media=gc_obj_media_water) then
                              begin
                                 if ((enemyplmask and gPlayer[TObj(pObj).pl].myplmask)=0) then // is friend
                                 begin
                                    if (gCustomObjPoints[cid][id].aabb.buse) then
                                    begin
                                       if (GetGameObjectRayCastCustAABBByHandle(goHnd, x1, tmpy1, z1, x2, tmpy2, z2, gCustomObjPoints[cid][id].aabb.minx, gCustomObjPoints[cid][id].aabb.miny, gCustomObjPoints[cid][id].aabb.minz, gCustomObjPoints[cid][id].aabb.maxx, gCustomObjPoints[cid][id].aabb.maxy, gCustomObjPoints[cid][id].aabb.maxz)) then
                                       begin
                                          Result := goHnd;
                                          break(MAIN);
                                       end;
                                    end
                                    else
                                    if GetGameObjectRayCastAABBByHandle(goHnd, x1, tmpy1, z1, x2, tmpy2, z2) then
                                    begin
                                       Result := goHnd;
                                       break(MAIN);
                                    end;
                                 end;
                              end;
                           end
                           else
                           ErrorLog('_misc_IsBuildingInRay try to get wrong baseid : state='+GetCurrentStateName+' filename='+GetStateMachineFileName(GetCurrentStateMachineHandle));
                        end;
                     end;
                  end;
               end;
               
               prevGridX := gridX;
               prevGridY := gridY;
            end;
         end;
         
         fCurGridX := fCurGridX + dirGridX;
         fCurGridY := fCurGridY + dirGridZ;
         
         curDist := curDist + gc_scangrid_tracestep / gc_scangrid_size;
      end;
   end;
   _misc_ProfilerEnd('ray');
end;

// _misc_CircleIntersectRect
//
function _misc_CircleIntersectRect(const x1, y1, x2, y2, x, y, r : Float) : Boolean;
begin
   if (y<y1) then // Если центр сверху
   begin
      if (x<x1) then // Если центр в левом углу
      Result := (((x-x1)*(x-x1)+(y-y1)*(y-y1))<=r*r)
      else
      if (x>x2) then // Если центр в правом углу
      Result := (((x-x2)*(x-x2)+(y-y1)*(y-y1))<=r*r)
      else
      Result := ((y1-y)<=r); // Если центр посередине
   end
   else
   if (y>y2) then // Если центр снизу
   begin
      if (x<x1) then // Если центр в левом углу
      Result := (((x-x1)*(x-x1)+(y-y2)*(y-y2))<=r*r)
      else
      if (x>x2) then // Если центр в правом углу
      Result := (((x-x2)*(x-x2)+(y-y2)*(y-y2))<=r*r)
      else
      Result := ((y-y2)<=r); // Если центр посередине
   end
   else
   begin
      if (x<x1) then // Если центр слева
      Result := ((x1-x)<=r)
      else
      if (x>x2) then // Если центр справа
      Result := (((x-x2)<=r))
      else
      Result := True; // внутри
      //Result := (((x-x1)<=r) or ((x2-x)<=r) or ((y-y1)<=r) or ((y2-y)<=r)); // внутри
   end;
end;

// _misc_IsCorrectScanCellOwner
//
function _misc_IsCorrectScanCellOwner(plInd, gridX, gridY : Integer) : Boolean;
begin
   Result := (not gbool_peacemode) or (gScanGrid[gridX, gridY].owner < 0) or (gScanGrid[gridX, gridY].owner = plInd);
end;

// _misc_IsCorrectIslandPosition
//
function _misc_IsCorrectIslandPosition(plInd : Integer; posX, posZ : Float) : Boolean;
begin
   var myIsland : Integer = _misc_GetIsland(gPlayer[plInd].aidata.centerx, gPlayer[plInd].aidata.centerz);
   var island : Integer = _misc_GetIsland(posx, posz);
   if island = myIsland then
   begin
      var gridX, gridY : Integer;
      if (_misc_PosToScanGridIndices(posX, posZ, gridX, gridY)) then
      Result := _misc_IsCorrectScanCellOwner(plInd, gridX, gridY)
      else
      Result := False;
   end
   else
   Result := False;
end;


// _misc_CalcScanCellsMinMax
//
procedure _misc_CalcScanCellsMinMax(x, y, rx1 : Integer; var left, top, right, bottom : Integer);
begin
   var cellx, celly : Integer;
   left := x-rx1;
   top := y-rx1;
   
   var rx2 : Integer = rx1+rx1;
   right := left+rx2;
   bottom := top+rx2;
   
   if (left<0) then left := 0
   else if (left>=gc_scangrid_countx) then left := gc_scangrid_countx-1;
   if (right<0) then right := 0
   else if (right>=gc_scangrid_countx) then right := gc_scangrid_countx-1;
   if (top<0) then top := 0
   else if (top>=gc_scangrid_county) then top := gc_scangrid_county-1;
   if (bottom<0) then bottom := 0
   else if (bottom>=gc_scangrid_county) then bottom := gc_scangrid_county-1;
end;

// _misc_MakeScanCellsList
//
procedure _misc_MakeScanCellsList(x, y, r : Float; plInd : Integer; var list : TIntegerList);
begin
   list.Clear;
   var rx1 : Integer = floor(r/gc_scangrid_size)+1;
   var x1, y1, x2, y2 : Float;
   var cellx, celly, cellxmax, cellymax : Integer;
   var gridX, gridY : Integer;
   if (_misc_PosToScanGridIndices(x, y, gridX, gridY)) then
   begin
      _misc_CalcScanCellsMinMax(gridX, gridY, rx1, cellx, celly, cellxmax, cellymax);
      
      var cx : Float = x+(GetMapHeight div 2);
      var cy : Float = y+(GetMapHeight div 2);
      
      var i, j : Integer;
      for i:=cellx to (cellxmax) do
      for j:=celly to (cellymax) do
      if _misc_IsCorrectScanCellOwner(plInd, i, j) then
      begin
         x1 := i*gc_scangrid_size;
         y1 := j*gc_scangrid_size;
         x2 := x1+gc_scangrid_size;
         y2 := y1+gc_scangrid_size;
         if (_misc_CircleIntersectRect(x1, y1, x2, y2, cx, cy, r)) then
         list.Add(j*gc_scangrid_county+i);
      end;
   end;
   //else
   //ErrorLog('_misc_MakeScanCellsList : position out of range');
end;

// _misc_CalcResCellsMinMax
//
procedure _misc_CalcResCellsMinMax(x, y, rx1 : Integer; var left, top, right, bottom : Integer);
begin
   var cellx, celly : Integer;
   left := x-rx1;
   top := y-rx1;
   
   var rx2 : Integer = rx1+rx1;
   right := left+rx2;
   bottom := top+rx2;
   
   if (left<0) then left := 0
   else if (left>=gc_resgrid_countx) then left := gc_resgrid_countx-1;
   if (right<0) then right := 0
   else if (right>=gc_resgrid_countx) then right := gc_resgrid_countx-1;
   if (top<0) then top := 0
   else if (top>=gc_resgrid_county) then top := gc_resgrid_county-1;
   if (bottom<0) then bottom := 0
   else if (bottom>=gc_resgrid_county) then bottom := gc_resgrid_county-1;
end;

// _misc_MakeListObjectsInRadiusOptimized
//
procedure _misc_MakeListObjectsInRadiusOptimized(x, y, r : Float; bcheckdist : Boolean; mmask, needcount : Integer; var list : TIntegerList);
begin
   list.Clear;
   var rmmask : Integer;
   if (mmask=0) then
   rmmask := $ffff
   else
   rmmask := mmask;
   var bLimitCount : Boolean = (needcount>0);
   gTmpIntListObjectsInRadius.Clear;
   _misc_MakeScanCellsList(x, y, r, -1, gTmpIntListObjectsInRadius);
   
   var trgHnd : Integer;
   var i, gridind, gridx, gridy, k, count : Integer;
   for [MAIN]i:=gTmpIntListObjectsInRadius.GetCount-1 downto 0 do
   begin
      gridind := gTmpIntListObjectsInRadius.Get(i);
      gridx := gridind mod gc_scangrid_county;
      gridy := gridind div gc_scangrid_county;
      if ((gScanGrid[gridx, gridy].fMask and rmmask)<>0) then
      for k:=gScanGrid[gridx, gridy].GetCount-1 downto 0 do
      begin
         trgHnd := gScanGrid[gridx, gridy].Get(k);
         if (trgHnd<>0) then
         begin
            var pobj : Pointer = _misc_GetObjectArgData(trgHnd, gc_argunit_obj);
            if (pobj<>nil) and ((gObjProp[TObj(pobj).cid][TObj(pobj).id].mmask and rmmask)<>0) then
            if (not bcheckdist) or (VectorDistance(GetGameObjectPositionXByHandle(trgHnd), 0, GetGameObjectPositionZByHandle(trgHnd), x, 0, y)<=r) then
            begin
               list.Add(trgHnd);
               count := count+1;
               if (bLimitCount) and (count>=needcount) then
               break(MAIN);
            end;
         end;
      end;
   end;
end;

// _misc_MakeListObjectsInRadiusBySpiral
//
procedure _misc_MakeListObjectsInRadiusBySpiral(plind : Integer; x, y, r : Float; bcheckdist : Boolean; mmask, needcount : Integer; bAddRadius : Boolean; var list : TIntegerList);
begin
   var rmmask : Integer;
   if (mmask=0) then
   rmmask := $ffff
   else
   rmmask := mmask;
   var trgHnd : Integer;
   var count : Integer;
   var rSqr : Float = r*r;
   procedure SpiralHelper(gridx, gridy : Integer; var list : TIntegerList);
   begin
      if (needcount>0) and (count>=needcount) then exit;
      var i : Integer;
      if ((gScanGrid[gridx, gridy].fMask and rmmask)<>0) and (gScanGrid[gridx, gridy].fPlMask and (1 shl plind)>0) then
      for i:=gScanGrid[gridx, gridy].GetCount-1 downto 0 do
      begin
         trgHnd := gScanGrid[gridx, gridy].Get(i);
         if (trgHnd<>0) then
         begin
            var pobj : Pointer = StateMachineGetArgDataByInd(GetGameObjectStateMachineHandle(trgHnd), gc_argunit_obj); {if (gProfile.bDbgLogPointerNil) then _init_DebugPointerNil(pobj);}
            if (pobj<>nil) and ((gObjProp[TObj(pobj).cid][TObj(pobj).id].mmask and rmmask)<>0) and (TObj(pobj).pl=plind) then
            begin
               if (not bcheckdist) or ((Sqr(GetGameObjectPositionXByHandle(trgHnd)-x)+Sqr(GetGameObjectPositionZByHandle(trgHnd)-y))<=rSqr) then
               begin
                  list.Add(trgHnd);
                  count := count+1;
                  if (needcount>0) and (count>=needcount) then
                  break;
               end;
            end;
         end;
      end;
   end;
   var rx1 : Integer = floor(r/gc_scangrid_size)+1;
   var gridX, gridY : Integer;
   if (_misc_PosToScanGridIndices(x, y, gridX, gridY)) then
   begin
      SpiralHelper(gridX, gridY{, mmask}, list);
      var i, rad : Integer;
      for [MAIN]rad:=1 to rx1 do
      begin
         for i:=-rad to rad do
         begin
            if (needcount>0) and (count>=needcount) then break(MAIN);
            if ((gridY+i)>0) and ((gridY+i)<gc_scangrid_county) then
            begin
               if ((gridX-rad)>0) and ((gridX-rad)<gc_scangrid_countx) then
               SpiralHelper(gridX-rad, gridY+i{, mmask}, list);
               if ((gridX+rad)>0) and ((gridX+rad)<gc_scangrid_countx) then
               SpiralHelper(gridX+rad, gridY+i{, mmask}, list);
            end;
         end;
         
         for i:=-rad to rad do
         begin
            if (needcount>0) and (count>=needcount) then break(MAIN);
            if ((gridX+i)>0) and ((gridX+i)<gc_scangrid_countx) then
            begin
               if ((gridY-rad)>0) and ((gridY-rad)<gc_scangrid_county) then
               SpiralHelper(gridX+i, gridY-rad{, mmask}, list);
               if ((gridY+rad)>0) and ((gridY+rad)<gc_scangrid_county) then
               SpiralHelper(gridX+i, gridY+rad{, mmask}, list);
            end;
         end;
      end;
   end;
end;

// _misc_MakeListObjectsInRadius
//
procedure _misc_MakeListObjectsInRadius(x, y, r : Float; bcheckdist : Boolean; mmask, needcount : Integer; bAddRadius : Boolean; var list : TIntegerList);
begin
   list.Clear;
   var gridX, gridY : Integer;
   if (_misc_PosToScanGridIndices(x, y, gridX, gridY)) then
   begin
      var rmmask : Integer;
      if (mmask=0) then
      rmmask := $ffff
      else
      rmmask := mmask;
      var bLimitCount : Boolean = (needcount>0);
      var rx1 : Integer = floor(r/gc_scangrid_size);
      if (bAddRadius) then rx1 := rx1+1;
      var cellx, celly, cellxmax, cellymax : Integer;
      _misc_CalcScanCellsMinMax(gridX, gridY, rx1, cellx, celly, cellxmax, cellymax);
      var trgHnd : Integer;
      var rSqr : Float = r*r;
      var i, j, k, count : Integer;
      for [MAIN]i:=cellx to (cellxmax) do
      for j:=celly to (cellymax) do
      begin
         if ((gScanGrid[i, j].fMask and rmmask)<>0) then
         for k:=gScanGrid[i, j].GetCount-1 downto 0 do
         begin
            trgHnd := gScanGrid[i, j].Get(k);
            if (trgHnd<>0) and (GetGameObjectVisibleByHandle(trgHnd)) then
            begin
               var pobj : Pointer = _misc_GetObjectArgData(trgHnd, gc_argunit_obj);
               if (pobj<>nil) and ((gObjProp[TObj(pobj).cid][TObj(pobj).id].mmask and rmmask)<>0) then
               if (not bcheckdist) or ((Sqr(GetGameObjectPositionXByHandle(trgHnd)-x)+Sqr(GetGameObjectPositionZByHandle(trgHnd)-y))<=rSqr) then
               begin
                  list.Add(trgHnd);
                  count := count+1;
                  if (bLimitCount) and (count>=needcount) then
                  break(MAIN);
               end;
            end;
         end;
      end;
   end;
   //else
   //ErrorLog('_misc_MakeListObjectsInRadius : position out of range');
end;

// _misc_MakeListEnvironmentInRadius
//
procedure _misc_MakeListEnvironmentInRadius(x, y, r : Float; bcheckdist : Boolean; bincludefood, bincludewood, bincludestone, bincludemines : Boolean; var list : TIntegerList);
begin
   list.Clear;
   var rx1 : Integer = floor(r/gc_resgrid_size)+1;
   var cellx, celly, cellxmax, cellymax : Integer;
   var gridX, gridY : Integer;
   if _misc_PosToResGridIndices(x, y, gridX, gridY) then
   begin
      _misc_CalcResCellsMinMax(gridX, gridY, rx1, cellx, celly, cellxmax, cellymax);
      var trgHnd : Integer;
      var rSqr : Float = r*r;
      var i, j, k : Integer;
      for i:=cellx to (cellxmax) do
      for j:=celly to (cellymax) do
      begin
         for k:=gResGrid[i, j].GetCount-1 downto 0 do
         begin
            trgHnd := gResGrid[i, j].Get(k);
            if (trgHnd<>0) then
            begin
               var pres : Pointer = _misc_GetObjectArgData(trgHnd, gc_argenv_res);
               if (pres<>nil) and (TBaseObj(pres).baseid=gc_baseid_res) then
               begin
                  case TRes(pres).itype of
                     gc_resource_type_food : begin
                        if (not bincludefood) then
                        continue;
                        //else
                        //if (not TRes(pres).brised) then
                        //continue;
                     end;
                     gc_resource_type_wood : if (not bincludewood) then continue;
                     gc_resource_type_stone : if (not bincludestone) then continue;
                     gc_resource_type_gold, gc_resource_type_iron, gc_resource_type_coal : if (not bincludemines) then continue;
                  end;
                  if (not bcheckdist) or ((Sqr(GetGameObjectPositionXByHandle(trgHnd)-x)+Sqr(GetGameObjectPositionZByHandle(trgHnd)-y))<=rSqr) then
                  list.Add(trgHnd);
               end;
            end;
         end;
      end;
   end;
   //else
   //ErrorLog('_misc_MakeListEnvironmentInRadius : position out of range');
end;

// _misc_FindResourceToExtract
//
function _misc_FindResourceToExtract(plInd : Integer; px, py : Float; var resHnd : Integer; var resx, resy : Float; filterres : Integer) : Boolean;
begin
   Result := False;
   var gridx, gridy : Integer;
   if (_misc_PosToResGridIndices(px, py, gridx, gridy)) then
   begin
      var mingridx, mingridy : Integer = -1;
      
      var woodcount, stonecount : Integer;
      var woodworker, stoneworker : Integer;
      var dstfactor : Float;
      var dst, reldst, minreldst : Float;
      
      var testW, testS, testWW, testWS : Integer;
      var testD : Float;
      
      var i, j, k : Integer;
      for i:=0 to gc_resgrid_countx-1 do
      for j:=0 to gc_resgrid_county-1 do
      begin
         if (filterres<>gc_resource_type_stone) then
         woodcount := gResGrid[i,j].fResCount[gc_resource_type_wood]*gc_gameplay_resource_maxattackers_wood;
         if (filterres<>gc_resource_type_wood) then
         stonecount := gResGrid[i,j].fResCount[gc_resource_type_stone]*gc_gameplay_resource_maxattackers_stone;
         
         //if ((woodcount+stonecount)>9) then
         if ((woodcount+stonecount)>4) then
         begin
            woodworker := gResGrid[i,j].fWorkerCount[plInd][gc_resource_type_wood];
            stoneworker := gResGrid[i,j].fWorkerCount[plInd][gc_resource_type_stone];
            dst := VectorDistance(gridx, 0, gridy, i, 0, j);
            dstfactor := 2+(Round(dst) div 1);
            
            var freewood : Integer = woodcount-woodworker;
            if (freewood<0) then freewood := 0;
            var freestone : Integer = stonecount-stoneworker;
            if (freestone<0) then freestone := 0;
            
            reldst := dstfactor/(1+(freewood+freestone/2)/40);
            if (freewood+freestone>1) and ((reldst<minreldst) or (minreldst=0)) then
            begin
               minreldst := reldst;
               mingridx := i;
               mingridy := j;
               testD := dst;
               testW := freewood;
               testS := freestone;
               testWW := woodworker;
               testWS := stoneworker;
            end;
         end;
      end;
      
      var newind : Integer;
      var rndind : Integer;
      var count : Integer = gResGrid[mingridx,mingridy].GetCount;
      if (count>0) then
      begin
         rndind := floor(random*count);
         for i:=0 to count-1 do
         begin
            newind := (rndind+i) mod count;
            var goHnd : Integer = gResGrid[mingridx,mingridy].Get(newind);
            var pres : Pointer = _misc_GetObjectArgData(goHnd, gc_argenv_res);
            if (pres<>nil) and (TBaseObj(pres).baseid=gc_baseid_res) then
            begin
               var tmpfilterres : Integer = filterres;
               if (filterres=gc_resource_type_none) then
               begin
                  if (random<(testW/(testW+testS))) then
                  tmpfilterres := gc_resource_type_wood
                  else
                  tmpfilterres := gc_resource_type_stone;
               end;
               
               if (tmpfilterres=gc_resource_type_none) or
               ((tmpfilterres=gc_resource_type_wood) and (TRes(pres).itype=gc_resource_type_wood)) or
               ((tmpfilterres=gc_resource_type_stone) and (TRes(pres).itype=gc_resource_type_stone)) then
               begin
                  resHnd := goHnd;
                  resx := GetGameObjectPositionXByHandle(goHnd);
                  resy := GetGameObjectPositionZByHandle(goHnd);
                  Result := True;
                  break;
               end;
            end
            else
            ErrorLog('_misc_FindResourceToExtract : pres=nil or wrong baseid');
         end;
      end;
   end;
end;

// _misc_UpdateTopZoneInfo
//
procedure _misc_UpdateTopZoneInfo(tzInd : Integer);
begin
   var pBuffer : Pointer = TopologyGetZoneBufferByIndex(tzInd);
   if (pBuffer <> nil) and TTopZone(pBuffer).changed then
   begin
      var pl, i : Integer;
      for pl := 0 to gc_MaxPlayerCount-1 do
      begin
         var chDanger : Float = TTopZone(pBuffer).changeDanger[pl];
         if chDanger <> 0 then
         begin
            var curDanger : Float = chDanger;
            var conCount : Integer = TopologyGetZoneConnectionsCountByIndex(tzInd);
            var curDist : Integer;
            for i := 0 to conCount-1 do
            begin
               var conInd : Integer = TopologyGetZoneConnectionByIndex(tzInd, i);
               var pConBuffer : Pointer = TopologyGetZoneBufferByIndex(conInd);
               if pConBuffer <> nil then
               begin
                  var conDist : Integer = TopologyGetZoneConnectionDistByIndex(tzInd, i);
                  if conDist <> curDist then
                  begin
                     curDanger := chDanger * Pow(gc_top_DangerDecFactor, conDist);
                     curDist := conDist;
                  end;
                  
                  TTopZone(pConBuffer).danger[pl] := TTopZone(pConBuffer).danger[pl] + curDanger;
               end;
            end;
            
            TTopZone(pBuffer).changeDanger[pl] := 0;
         end;
         
         var chAttract : Float = TTopZone(pBuffer).changeAttract[pl];
         if chAttract <> 0 then
         begin
            TTopZone(pBuffer).attract[pl] := TTopZone(pBuffer).attract[pl] + chAttract;
            TTopZone(pBuffer).changeAttract[pl] := 0;
         end;
      end;
      TTopZone(pBuffer).changed := false;
   end;
end;

// _misc_UpdateTopZonesInfo
//
procedure _misc_UpdateTopZonesInfo;
begin
   if gInt_MinTopZoneChanged <= gInt_MaxTopZoneChanged then
   begin
      var i : Integer;
      for i := gInt_MinTopZoneChanged to gInt_MaxTopZoneChanged do
      begin
         var pBuffer : Pointer = TopologyGetZoneBufferByIndex(i);
         if (pBuffer <> nil) and TTopZone(pBuffer).changed then
         _misc_UpdateTopZoneInfo(i);
      end;
      
      gInt_MinTopZoneChanged := TopologyGetZonesCount;
      gInt_MaxTopZoneChanged := -1;
   end;
end;

// _misc_DisbandSquad
//
procedure _misc_DisbandSquad(plHnd, sqInd : Integer; blan : Boolean);
begin
   if plHnd <> 0 then
   begin
      var plInd : Integer = GetPlayerIndexByHandle(plHnd);
      var pSquad : Pointer = gPlayer[plInd].squads.Get(sqInd);
      if pSquad <> nil then
      begin
         TSquad(pSquad).fSelected := False;
         
         // network. do before disband
         if blan and (_net_IsOnline or _net_IsRecord) then begin
            var parg: Pointer = StateMachineGetArgDataByInd(GetPlayerStateMachineHandle(plhnd), gc_argplayer_plr); if (gProfile.bDbgLogPointerNil) then _init_DebugPointerNil(parg);
            if parg<>nil then begin
               gWriteSquadPtrList.Clear;
               gWriteSquadPtrList.AddPointer(psquad);
               gIntListWriteSquadAction.Clear;
               TPlayerArgs(parg).fpointer0 := gWriteSquadPtrList;
               TPlayerArgs(parg).fordtyp := gc_playersquad_action_disband;
               TPlayerArgs(parg).fbstate := False;
               TPlayerArgs(parg).fform := 0;
               TPlayerArgs(parg).fintlst := gIntListWriteSquadAction;
               PlayerExecuteStateByHandle(plhnd, 'WriteSquadListAction');
            end else _misc_ErrorLog('_misc_DisbandSquad: parg = nil');
         end;
         
         if _net_IsOffline or _net_IsServer or{(blan and _net_IsServer) or} ((not blan) and _net_IsClient) then
         begin
            var grName : String = TSquad(pSquad).fGroupName;
            var grHandle : Integer = GetPlayerGrHandleByHandle(plHnd, grName);
            
            if plHnd = GetPlayerHandleInterfaceIO then
            begin
               GroupSetPickedByHandle(grHandle, false);
               gSelectedSquads.Remove(pSquad);
            end;
            
            RemoveGroupByHandle(grHandle);
            gPlayer[plInd].DisbandSquad(sqInd);
         end;
      end;
   end;
end;

// _misc_IsProcessAI
//
function _misc_IsProcessAI: Boolean;
begin
   result:=(_net_IsOffline or _net_IsServer) and not _net_IsReplay;
end;

//_misc_IntersectRects
//
function _misc_IntersectRects(afloatrect1, afloatrect2 : TRect) : Boolean;
begin
   Result := not
   ((afloatrect1.minx>afloatrect2.maxx) or
   (afloatrect1.miny>afloatrect2.maxy) or
   
   (afloatrect2.minx>afloatrect1.maxx) or
   (afloatrect2.miny>afloatrect1.maxy));
end;

// _misc_IntersectBox
//
procedure _misc_IntersectBox(const rect1: TBox; const rect2: TBox; var arect: TBox);
begin
   arect.minx:=rect1.minx;
   arect.maxx:=rect1.maxx;
   arect.miny:=rect1.miny;
   arect.maxy:=rect1.maxy;
   arect.minz:=rect1.minz;
   arect.maxz:=rect1.maxz;
   if (arect.minx>rect2.maxx) or (arect.maxx<rect2.minx) or (arect.miny>rect2.maxy) or (arect.maxy<rect2.miny) or (arect.minz>rect2.maxz) or (arect.maxz<rect2.minz) then
   begin
      // no intersection
      const a = 0;
      arect.minx:=a;
      arect.maxx:=a;
      arect.miny:=a;
      arect.maxy:=a;
      arect.minz:=a;
      arect.maxz:=a;
   end
   else
   begin
      if arect.minx<rect2.minx then
      arect.minx:=rect2.minx;
      if arect.maxx>rect2.maxx then
      arect.maxx:=rect2.maxx;
      if arect.miny<rect2.miny then
      arect.miny:=rect2.miny;
      if arect.maxy>rect2.maxy then
      arect.maxy:=rect2.maxy;
      if arect.minz<rect2.minz then
      arect.minz:=rect2.minz;
      if arect.maxz>rect2.maxz then
      arect.maxz:=rect2.maxz;
   end;
end;

// _misc_Get3DRectVolume
//
function _misc_GetBoxVolume(var rect: TBox) : Float;
begin
   Result := Abs(rect.maxx-rect.minx)*Abs(rect.maxy-rect.miny)*Abs(rect.maxz-rect.minz);
end;

// _misc_GetTeamEnemyInfo
//
function _misc_GetTeamEnemyInfo(team : Integer) : Pointer;
begin
   Result := nil;
   var i : Integer;
   var count : Integer = gEnemyInfoList.GetCount;
   for i := 0 to count-1 do
   begin
      var pEnemyInfo : Pointer = gEnemyInfoList.Get(i);
      if TEnemyInfo(pEnemyInfo).team = team then
      begin
         Result := pEnemyInfo;
         break;
      end;
   end;
end;

// _misc_GetPlayerEnemyInfo
//
function _misc_GetPlayerEnemyInfo(plInd : Integer) : Pointer;
begin
   Result := nil;
   if IsInRangeInt(plInd, 0, gc_MaxPlayerCount-1) then
   Result := _misc_GetTeamEnemyInfo(gPlayer[plInd].team);
end;

// _misc_CreateBorders
//
procedure _misc_CreateBorders();
begin
   SetTerrainBordersVisible(True);
   SetTerrainBordersEnabled(True);
   if (not GetProjectOptionAsBoolean('HorizonRenderEnabled')) then
   SetProjectOptionAsBoolean('HorizonRenderEnabled', True);
   
   const cRadius = 24;
   
   var x, y : Float;
   _misc_GetMapLeftTopPos(x, y);
   
   var width : Float = GetMapWidth+cRadius*2;
   var height : Float = GetMapHeight+cRadius*2;
   var maxxind : Integer = floor(width/cRadius);
   if (width>(maxxind*cRadius)) then
   maxxind := maxxind+1;
   
   var maxyind : Integer = floor(height/cRadius);
   if (height>(maxyind*cRadius)) then
   maxyind := maxyind+1;
   
   var i : Integer;
   for i:=-1 to maxxind-2 do
   begin
      var px : Float = x+i*cRadius+(cRadius/2);
      var py : Float = y-cRadius*2/2;
      PaintTerrain(px, -py, 86, -1, -1, False, -1, -1, cRadius, False, True);
      
      px := x+i*cRadius+(cRadius/2);
      py := -(y-(cRadius/2));
      PaintTerrain(px, -py-0.5, 86, -1, -1, False, -1, -1, cRadius, False, True);
   end;
   for i:=-1 to maxyind-2 do
   begin
      var px : Float = x-(cRadius/2);
      var py : Float = y+i*cRadius+(cRadius/2);
      PaintTerrain(px+0.5, -py, 86, -1, -1, False, -1, -1, cRadius, False, True);
      
      px := -(x-(cRadius/2));
      py := y+i*cRadius+(cRadius/2);
      PaintTerrain(px, -py, 86, -1, -1, False, -1, -1, cRadius, False, True);
   end;
end;

// _misc_CreateBordersWithDecal
//
procedure _misc_CreateBordersWithDecal();
begin
   const cDecalSize = 15;
   const decalname = gc_decalname_border;
   
   var x, y : Float;
   _misc_GetMapLeftTopPos(x, y);
   
   var maxxind : Integer = floor(GetMapWidth/cDecalSize);
   if (GetMapWidth>(maxxind*cDecalSize)) then
   maxxind := maxxind+1;
   
   var maxyind : Integer = floor(GetMapHeight/cDecalSize);
   if (GetMapHeight>(maxyind*cDecalSize)) then
   maxyind := maxyind+1;
   
   var i : Integer;
   for i:=0 to maxxind-1 do
   begin
      var px : Float = x+i*cDecalSize+(cDecalSize/2);
      var py : Float = y+(cDecalSize/2);
      PutDecalByName(px, py, decalname);
      MapDrawCollision(px, py, 101, cDecalSize, False);
      
      px := x+i*cDecalSize+(cDecalSize/2);
      py := -(y+(cDecalSize/2));
      PutDecalByName(px, py, decalname);
      MapDrawCollision(px, py-0.5, 101, cDecalSize, False);
   end;
   for i:=0 to maxyind-1 do
   begin
      var px : Float = x+(cDecalSize/2);
      var py : Float = y+i*cDecalSize+(cDecalSize/2);
      PutDecalByName(px, py, decalname);
      MapDrawCollision(px, py, 101, cDecalSize, False);
      
      px := -(x+(cDecalSize/2));
      py := y+i*cDecalSize+(cDecalSize/2);
      PutDecalByName(px, py, decalname);
      MapDrawCollision(px-0.5, py, 101, cDecalSize, False);
   end;
end;

// _misc_LoadMap
//
procedure _misc_LoadMap(var filename : String);
begin
   UserProfileLoadMap(filename);
end;

// _misc_SaveMap
//
procedure _misc_SaveMap(var filename : String; bautosave : Boolean);
begin
   procedure GetSaveFileNameByEnum(var tmpSaveMapFileName : String; saveMapFileName, ext : String; enum : Integer);
   begin
      tmpSaveMapFileName := saveMapFileName + '.autosave.' + IntToStr(enum) + ext;
   end;
   
   if (bautosave) then
   begin
      var currMapFileName : String = GetCurrentMapFileName;
      var saveMapFileName : String = filename;
      var saveMapFilePath : String = ExtractFilePath(currMapFileName);
      if StrExists(saveMapFileName, '.map') then
      begin
         var iPos : Integer = StrPos('.map', saveMapFileName);
         var iLength : Integer = StrLength(saveMapFileName);
         var extension : String = SubStr(saveMapFileName, iPos, iLength-iPos+1);
         saveMapFileName := StrReplace(saveMapFileName, extension, '');
      end;
      
      var fullMapFilePathName, tmpSaveMapFileName : String;
      
      var enum : Integer;
      for enum := 5 downto 1 do
      begin
         GetSaveFileNameByEnum(tmpSaveMapFileName, saveMapFileName, '.map', enum);
         fullMapFilePathName := saveMapFilePath + tmpSaveMapFileName;
         if enum = 5 then //It is too old, remove it
         begin
            if IsFileExists(fullMapFilePathName) then
            DeleteFileStream(fullMapFilePathName);
         end
         else
         begin
            if IsFileExists(fullMapFilePathName) then
            MoveFileStream(fullMapFilePathName, saveMapFilePath + saveMapFileName + '.autosave.' + IntToStr(enum+1) + '.map');
         end;
      end;
      
      filename := saveMapFileName + '.autosave.' + IntToStr(1) + '.map';
   end;
   gbool_gui_saving := True;
   var pause : Boolean = GetProgressingPauseMode();
   if (not pause) then
   begin
      SetProgressingPauseMode(True);
      GUIExecuteState('ShowPause');
      if (not GetProgressPlayersThread) and (not GetGUIProgressControlThread) then
      ViewerBufferRender;
   end;
   if ((_net_IsOnline) and (_net_IsServer)) then
   GUIExecuteState('WriteSave');
   UserProfileSaveMap(filename);
   UserProfileRefreshSavesList;
   //if (not IsFileExists(gMap.name)) then
   //UserProfileSaveReplay(ExtractFileName(filename)+'.autosave', gMap.name);
   
   gbool_gui_saving := False;
   if (not pause) then
   begin
      SetProgressingPauseMode(False);
      GUIExecuteState('ShowPause');
   end;
end;

// _misc_StringToIntList - works as Key|Value, key not stored in list
//
function _misc_StringToIntList(var s : String; var list : TIntegerList) : Boolean;
begin
   Result := True;
   list.Clear;
   var count : Integer = GetDelimiterStringCount(s, gc_gui_delimiterchar, gc_gui_quotechar);
   if (count>0) then
   begin
      var i : Integer;
      for i:=0 to count-1 do
      begin
         var value : String = GetDelimiterStringByIndex(s, gc_gui_delimiterchar, gc_gui_quotechar, i);
         list.add(strtoint(value))
      end;
   end
   else
   Result := False;
end;

// _misc_GetNearestTopZone
//
function _misc_GetNearestTopZone(var list : TIntegerList; zone : Integer) : Integer;
begin
   Result := -1;
   if list.GetCount > 0 then
   begin
      var i : Integer;
      for i := TopologyGetZonesCount-1 downto 0 do
      TopologySetZoneNeedPathByIndex(i, false);
      
      for i := list.GetCount-1 downto 0 do
      begin
         var ind : Integer = list.Get(i);
         TopologySetZoneNeedPathByIndex(ind, true);
      end;
      
      TopologyGetPathToZone(zone);
      
      var minDist : Integer = gc_MaxInt;
      for i := list.GetCount-1 downto 0 do
      begin
         var ind : Integer = list.Get(i);
         var tpCount : Integer = TopologyGetZoneTrackPointsCountByIndex(ind);
         if IsInRangeInt(tpCount, 1, minDist-1) then
         begin
            Result := ind;
            minDist := tpCount;
         end;
      end;
   end;
end;

// _misc_GetPathToAllZones
//
procedure _misc_GetPathToAllZones(zone : Integer);
begin
   var i : Integer;
   for i := TopologyGetZonesCount-1 downto 0 do
   TopologySetZoneNeedPathByIndex(i, (i<>zone));
   
   TopologyGetPathToZone(zone);
end;

// _misc_SelectRecordManagerGeneratorParser
//
function _misc_SelectRecordManagerGeneratorParser(bTemporary : Boolean) : Integer;
begin
   Result := 0;
   var pRM : Integer;
   if (bTemporary) then
   begin
      pRM := ParserSelectByKey('rm');
      if (pRM=0) then
      pRM := ParserCreate('rm');
   end
   else
   pRM := ParserSelectRecordManager;
   if (pRM<>0) then
   begin
      Result := ParserSelectByHandleByKey(pRM, 'Generator');
      if (Result=0) then
      Result := ParserAddChildByIndex(pRM, 'Generator');
   end
   else
   ErrorLog('_misc_SelectRecordManagerGeneratorParser : pRecordManager = 0');
end;

// _misc_SelectRecordManagerPatternListParser
//
function _misc_SelectRecordManagerPatternListParser(bTemporary : Boolean) : Integer;
begin
   Result := 0;
   var pRM : Integer;
   if (bTemporary) then
   begin
      pRM := ParserSelectByKey('rm');
      if (pRM=0) then
      pRM := ParserCreate('rm');
   end
   else
   pRM := ParserSelectRecordManager;
   if (pRM<>0) then
   begin
      var pRMGenerator : Integer = ParserSelectByHandleByKey(pRM, 'Generator');
      if (pRMGenerator=0) then
      pRMGenerator := ParserAddChildByIndex(pRM, 'Generator');
      var pPatternList : Integer = ParserSelectByHandleByKey(pRMGenerator, 'PatternList');
      if (pPatternList=0) then
      pPatternList := ParserAddChildByIndex(pRMGenerator, 'PatternList');
      Result := pPatternList;
   end
   else
   ErrorLog('_misc_SelectRecordManagerPatternListParser : pRecordManager = 0');
end;

// _misc_WritePatternToParser
//
procedure _misc_WritePatternToParser(pHnd : Integer; var name : String; x, y : Float);
begin
   if (pHnd<>0) then
   begin
      var pChild : Integer = ParserAddChildByIndex(pHnd, '*');
      ParserSetValueByKeyByHandle(pChild, 'n', name);
      ParserSetFloatValueByKeyByHandle(pChild, 'x', x);
      ParserSetFloatValueByKeyByHandle(pChild, 'y', y);
   end;
end;

// _misc_StandPatternPlaceMask
//
procedure _misc_StandPatternPlaceMask(patName : String; standX, standZ, angle : Float; var tmpPatternMask : array [0..639] of array [0..639] of Boolean);
begin
   // be sure, thet LoadPatterns(true, true) is done, or maskStand will return false value, for not preloaded patterns
   var angleRad : Float = angle*pi/180;
   
   var mWidth : Integer = GetPatternMaskWidth(patName);
   var mHeight : Integer = GetPatternMaskHeight(patName);
   var offX : Float = GetPatternOffsetX(patName);
   var offZ : Float = GetPatternOffsetZ(patName);
   var x, z : Float;
   
   if Abs(angleRad - pi/2 * Round(angleRad / (pi/2))) < 0.000001 then
   begin
      var offY : Float;
      VectorRotateY(offX, offY, offZ, -angle);
      x := StandX + offX;
      z := StandZ + offZ;
   end
   else
   begin
      x := StandX + offX;
      z := StandZ + offZ;
   end;
   
   var w : Integer = (mWidth - 1) div 2;
   var h : Integer = (mHeight - 1) div 2;
   var vMinX : Integer = Floor(x - w);
   var vMinY : Integer = Floor(z - h);
   var vMaxX : Integer = vMinX + mWidth - 1;
   var vMaxY : Integer = vMinY + mHeight - 1;
   var mapW : Integer = GetMapWidth;
   var mapH : Integer = GetMapHeight;
   
   var cx : Float = Floor((vMinX + vMaxX) / 2) + 0.5;
   var cy : Float = Floor((vMinY + vMaxY) / 2) + 0.5;
   
   var maskStand : Boolean = GetPatternMaskStand(patName);
   var maskInvStand : Boolean = GetPatternInvMaskStand(patName);
   var i, j : Integer;
   for [MAIN]i := vMinY to vMaxY - 1 do
   for j := vMinX to vMaxX - 1 do
   begin
      var fi : Integer = Round(Round(cos(angleRad))*(i - cy + 0.5) + Round(sin(angleRad))*(j - cx + 0.5) + cy - 0.5);
      var fj : Integer = Round(Round(cos(angleRad))*(j - cx + 0.5) - Round(sin(angleRad))*(i - cy + 0.5) + cx - 0.5);
      
      var maskValue : Boolean = GetPatternMaskValue(patName, i-vMinY, j-vMinX);
      if (maskValue and maskStand) or ((not maskValue) and maskInvStand) then
      begin
         var indI : Integer = fi + mapH div 2;
         var indJ : Integer = fj + mapW div 2;
         
         if (indI>=0) and (indI<mapH) and (indJ>=0) and (indJ<mapW) and (not tmpPatternMask[indJ, indI]) then
         tmpPatternMask[indJ, indI] := true
      end;
   end;
end;

// _misc_CheckStandPattern
//
function _misc_CheckStandPattern(patName : String; plHnd : Integer; standX, standZ, angle : Float; doStand, bWriteToParser : Boolean; pHnd : Integer) : Boolean;
begin
   // be sure, thet LoadPatterns(true, true) is done, or maskStand will return false value, for not preloaded patterns
   var angleRad : Float = angle*pi/180;
   if doStand then
   begin
      StandPatternWithAngle(plHnd, patName, standX, standZ, angleRad, 0, 90);
      if (bWriteToParser) then
      begin
         var pPatternList : Integer;
         if (pHnd=0) then
         pPatternList := _misc_SelectRecordManagerPatternListParser(True)
         else
         pPatternList := pHnd;
         _misc_WritePatternToParser(pPatternList, patName, standX, standZ);
      end;
   end;
   
   var mWidth : Integer = GetPatternMaskWidth(patName);
   var mHeight : Integer = GetPatternMaskHeight(patName);
   var offX : Float = GetPatternOffsetX(patName);
   var offZ : Float = GetPatternOffsetZ(patName);
   var x, z : Float;
   
   if Abs(angleRad - pi/2 * Round(angleRad / (pi/2))) < 0.000001 then
   begin
      var offY : Float;
      VectorRotateY(offX, offY, offZ, -angle);
      x := StandX + offX;
      z := StandZ + offZ;
   end
   else
   begin
      x := StandX + offX;
      z := StandZ + offZ;
   end;
   
   var w : Integer = (mWidth - 1) div 2;
   var h : Integer = (mHeight - 1) div 2;
   var vMinX : Integer = Floor(x - w);
   var vMinY : Integer = Floor(z - h);
   var vMaxX : Integer = vMinX + mWidth - 1;
   var vMaxY : Integer = vMinY + mHeight - 1;
   var mapW : Integer = GetMapWidth;
   var mapH : Integer = GetMapHeight;
   
   var cx : Float = Floor((vMinX + vMaxX) / 2) + 0.5;
   var cy : Float = Floor((vMinY + vMaxY) / 2) + 0.5;
   
   Result := true;
   var maskStand : Boolean = GetPatternMaskStand(patName);
   var maskInvStand : Boolean = GetPatternInvMaskStand(patName);
   var i, j : Integer;
   
   // fast optimized test
   if (not doStand) and (angle=0) then
   begin
      const step = 4;
      var optW : Integer = mWidth div step;
      var optH : Integer = mHeight div step;
      var optYH : Integer = vMinY+mapH div 2;
      var optXW : Integer = vMinX+mapW div 2;
      for [MAIN]i:=0 to optH do
      for j:=0 to optW do
      begin
         var fi, fj : Integer;
         if i=optH then
         fi := Min(i*step, mHeight)
         else
         fi := i*step;
         
         if j=optW then
         fj := Min(j*step, mWidth)
         else
         fj := j*step;
         
         var maskValue : Boolean = GetPatternMaskValue(patName, fi, fj);
         if (maskValue and maskStand) or ((not maskValue) and maskInvStand) then
         begin
            var indI : Integer = optYH+fi;
            var indJ : Integer = optXW+fj;
            
            if (indI<0) or (indI>=mapH) or (indJ<0) or (indJ>=mapW) or (gPatternMask[indJ, indI]) then
            begin
               Result := false;
               break(MAIN);
            end;
         end;
      end;
   end;
   
   if (Result) then
   for [MAIN]i := vMinY to vMaxY - 1 do
   for j := vMinX to vMaxX - 1 do
   begin
      var fi, fj : Integer;
      if (angle=0) then
      begin
         fi := i;
         fj := j;
      end
      else
      begin
         fi := Round(Round(cos(angleRad))*(i - cy + 0.5) + Round(sin(angleRad))*(j - cx + 0.5) + cy - 0.5);
         fj := Round(Round(cos(angleRad))*(j - cx + 0.5) - Round(sin(angleRad))*(i - cy + 0.5) + cx - 0.5);
      end;
      
      var maskValue : Boolean = GetPatternMaskValue(patName, i-vMinY, j-vMinX);
      if (maskValue and maskStand) or ((not maskValue) and maskInvStand) then
      begin
         var indI : Integer = fi + mapH div 2;
         var indJ : Integer = fj + mapW div 2;
         
         if doStand then
         begin
            if (indI>=0) and (indI<mapH) and (indJ>=0) and (indJ<mapW) and (not gPatternMask[indJ, indI]) then
            gPatternMask[indJ, indI] := true
         end
         else
         begin
            if (indI<0) or (indI>=mapH) or (indJ<0) or (indJ>=mapW) or (gPatternMask[indJ, indI]) then
            begin
               Result := false;
               break(MAIN);
            end;
         end;
      end;
   end;
end;

// _misc_CheckStandPatternExt
//
function _misc_CheckStandPatternExt(envplHnd : Integer; patName : String; standX, standZ : Float; bWriteToParser : Boolean; pHnd : Integer) : Boolean;
begin
   Result := (envplHnd<>0) and (_misc_CheckStandPattern(patName, envplHnd, standX, standZ, 0, False, False, 0));
   if (Result) then
   _misc_CheckStandPattern(patName, envplHnd, standX, standZ, 0, True, bWriteToParser, pHnd);
end;

// _misc_GetOrCreateGeneratorParser
//
function _misc_GetOrCreateGeneratorParser(bReload : Boolean) : Integer;
begin
   var pGenerator : Integer = _parser_ParserGenerator(bReload);
   Result := pGenerator;
   if (bReload) or (ParserGetCountByHandle(pGenerator)=0) then
   ParserLoadFromFileByHandle(pGenerator, gc_filepath_generator);
   
   var pPatterns : Integer = ParserSelectByHandleByKey(pGenerator, 'Patterns');
   if (pPatterns<>0) then
   begin
      var i, j : Integer;
      for i:=0 to ParserGetCountByHandle(pPatterns)-1 do
      begin
         var freqsum : Float;
         var pPatternGroup : Integer = ParserSelectByHandleByIndex(pPatterns, i);
         if (pPatternGroup<>0) then
         begin
            var pPatternList : Integer = ParserSelectByHandleByKey(pPatternGroup, 'PatternList');
            if (pPatternList<>0) then
            begin
               for j:=0 to ParserGetCountByHandle(pPatternList)-1 do
               begin
                  var pPattern : Integer = ParserSelectByHandleByIndex(pPatternList, j);
                  if (pPattern<>0) then
                  begin
                     if (ParserGetValueByKeyByHandle(pPattern, 'Freq')='') then
                     begin
                        ParserSetFloatValueByKeyByHandle(pPattern, 'Freq', 1);
                        freqsum := freqsum+1;
                     end
                     else
                     freqsum := freqsum+ParserGetFloatValueByKeyByHandle(pPattern, 'Freq');
                  end;
               end;
            end;
         end;
         ParserSetFloatValueByKeyByHandle(pPatternGroup, 'FreqSum', freqsum);
      end;
   end;
end;

// _misc_SelectPatternListParser
//
function _misc_SelectPatternListParser(pGenerator : Integer; const name : String) : Integer;
begin
   Result := 0;
   var pPatterns : Integer = ParserSelectByHandleByKey(pGenerator, 'Patterns');
   var i : Integer;
   for i:=0 to ParserGetCountByHandle(pPatterns)-1 do
   begin
      var pPatternGroup : Integer = ParserSelectByHandleByIndex(pPatterns, i);
      if (ParserGetValueByKeyByHandle(pPatternGroup, 'Name')=name) then
      begin
         Result := ParserSelectByHandleByKey(pPatternGroup, 'PatternList');
         break;
      end;
   end;
   if (Result=0) then
   Log('SelectPatternListParser : unknown pattern group = '+name);
end;

// _misc_PatternIncreaseFreq
//
function _misc_PatternIncreaseFreq(pGenerator : Integer; const str, name : String) : Integer;
begin
   const cMinFreq = 0.0001;
   var pPatterns : Integer = ParserSelectByHandleByKey(pGenerator, 'Patterns');
   var pPatternList, pPatternGroup : Integer;
   var i : Integer;
   for i:=0 to ParserGetCountByHandle(pPatterns)-1 do
   begin
      pPatternGroup := ParserSelectByHandleByIndex(pPatterns, i);
      if (ParserGetValueByKeyByHandle(pPatternGroup, 'Name')=str) then
      begin
         pPatternList := ParserSelectByHandleByKey(pPatternGroup, 'PatternList');
         break;
      end
      else
      pPatternGroup := 0;
   end;
   if (pPatternList<>0) then
   begin
      for i:=0 to ParserGetCountByHandle(pPatternList)-1 do
      begin
         var pPattern : Integer = ParserSelectByHandleByIndex(pPatternList, i);
         if (pPattern<>0) then
         begin
            if (ParserGetValueByKeyByHandle(pPattern, 'Pattern')=name) then
            begin
               var oldfreq : Float = ParserGetFloatValueByKeyByHandle(pPattern, 'Freq');
               var newfreq : Float = oldfreq*0.2;
               if newfreq<cMinFreq then
               newfreq := cMinFreq;
               ParserSetFloatValueByKeyByHandle(pPattern, 'Freq', newfreq);
               var usedcount : Integer = ParserGetIntValueByKeyByHandle(pPattern, 'UsedCount');
               ParserSetIntValueByKeyByHandle(pPattern, 'UsedCount', usedcount+1);
               var sumfreq : Float = ParserGetFloatValueByKeyByHandle(pPatternGroup, 'FreqSum');
               ParserSetFloatValueByKeyByHandle(pPatternGroup, 'FreqSum', (sumfreq-oldfreq+newfreq));
               var totalcount : Integer = ParserGetIntValueByKeyByHandle(pPatternGroup, 'TotalCount');
               ParserSetIntValueByKeyByHandle(pPatternGroup, 'TotalCount', totalcount+1);
               break;
            end;
         end;
      end;
   end;
end;

// _misc_GetPatternNameByParser
//
procedure _misc_GetPatternNameByParser(var name : String; pPatternList : Integer);
begin
   name := '';
   var pPatternGroup : Integer = ParserGetParent(pPatternList);
   var freqsum : Float = ParserGetFloatValueByKeyByHandle(pPatternGroup, 'FreqSum');
   var freqrnd : Float = RandomExt*freqsum;
   var i : Integer;
   for i:=0 to ParserGetCountByHandle(pPatternList)-1 do
   begin
      var pPattern : Integer = ParserSelectByHandleByIndex(pPatternList, i);
      var freq : Float = ParserGetFloatValueByKeyByHandle(pPattern, 'Freq');
      if (freqrnd<freq) then
      begin
         name := ParserGetValueByKeyByHandle(pPattern, 'Pattern');
         break;
      end
      else
      freqrnd := freqrnd-freq;
   end;
end;

// _misc_PlacePatternByType
//
procedure _misc_PlacePatternByType(pGenerator, envplHnd : Integer; const str : String; x, y : Float);
begin
   var pPatternList : Integer = _misc_SelectPatternListParser(pGenerator, str);
   if (pPatternList<>0) then
   begin
      var trycount : Integer = 8;
      var name : String;
      _misc_GetPatternNameByParser(name, pPatternList);
      var i : Integer;
      if (name<>'') then
      for i:=0 to trycount-1 do
      begin
         if (_misc_CheckStandPatternExt(envplHnd, name, x, y, False, 0)) then
         begin
            log('name='+name+' try='+IntToStr(i)+' x='+FloatToStr(x)+' y='+FloatToStr(y));
            _misc_PatternIncreaseFreq(pGenerator, str, name);
            break;
         end;
      end;
   end;
end;

// _misc_SetupPatternsByType
//
procedure _misc_SetupPatternsByType(pGenerator, envplHnd : Integer; const str : String; freq : Float);
begin
   var realcount, needed, neededround, badtries : Integer;
   var mapwidth : Integer = GetMapWidth;
   var mapheight : Integer = GetMapHeight;
   var mapsizeoptimise : Float = (320*320)/(mapwidth*mapheight);
   if (mapsizeoptimise<1) then
   mapsizeoptimise := mapsizeoptimise*0.65;
   var x0 : Integer = -(mapwidth div 2);
   var y0 : Integer = -(mapheight div 2);
   var pPatternList : Integer = _misc_SelectPatternListParser(pGenerator, str);
   var pRMHnd : Integer = _misc_SelectRecordManagerPatternListParser(True);
   if (pPatternList<>0) then
   begin
      needed := floor(mapwidth*mapheight*freq);
      neededround := round(mapwidth*mapheight*freq);
      if (needed=0) then
      begin
         if (freq>0) then
         needed := neededround;
         if (needed>0) then
         Log('_misc_SetupPatternsByType : str = '+str+' needed count = 0. freq is too small value. Needed value is rounded to 1');
      end;
      var trycount : Integer = floor(256*mapsizeoptimise);
      var i, j : Integer;
      for i:=0 to needed-1 do
      begin
         var name : String;
         _misc_GetPatternNameByParser(name, pPatternList);
         if (name<>'') then
         for j:=0 to trycount-1 do
         begin
            //if (j=(trycount-1)) then log('!!!bad str='+str+' name='+name+' freq='+FloatToStr(freq));
            var x : Float = floor(x0+RandomExt*mapwidth);
            var y : Float = floor(y0+RandomExt*mapheight);
            
            if (_misc_CheckStandPatternExt(envplHnd, name, x, y, True, pRMHnd)) then
            begin
               //log('str='+str+' name='+name+' try='+IntToStr(j)+' x='+FloatToStr(x)+' y='+FloatToStr(y));
               ApplicationProcessMessages;
               _misc_PatternIncreaseFreq(pGenerator, str, name);
               realcount := realcount+1;
               break;
            end
            else
            badtries := badtries+1;
         end;
      end;
   end
   else
   Log('SetupPatternsByType : wrong str = '+str);
   ApplicationProcessMessages;
   //if (realcount=0) and (needed>0) then
   //TimeLog('_misc_SetupPatternsByType : str = '+str+' cant find place to stand any pattern. realcount = 0. neededcount = '+IntToStr(needed)+' . badtries = '+IntToStr(badtries)+' freq = '+FloatToStr(freq))
   //else
   //TimeLog('_misc_SetupPatternsByType : str = '+str+' realcount = '+IntToStr(realcount)+ '; neededcount = '+IntToStr(needed)+' ; badtries = '+IntToStr(badtries)+' freq = '+FloatToStr(freq));
end;

// _misc_FixCollisionInertiaObjectsInOnePoint
//
procedure _misc_FixCollisionInertiaObjectsInOnePoint();
begin
   // fix issue when game may stuck on path trace
   var tries : Integer;
   for [MAIN]tries:=0 to 2 do
   if (GetCountOfPlayers>gc_playerind_env) then
   begin
      var bfound : Boolean;
      const epsilon = 0.001;
      var i, j : Integer;
      var plhnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
      if (plhnd<>0) then
      for i:=GetPlayerGameObjectsCountByHandle(plhnd)-1 downto 0 do
      begin
         var goHnd : Integer = GetGameObjectHandleByIndex(i, plhnd);
         if (GetGameObjectCollisionDetectionByHandle(goHnd)) then
         begin
            var px : Float = GetGameObjectPositionXByHandle(goHnd);
            var pz : Float = GetGameObjectPositionZByHandle(goHnd);
            GetGameObjectsInRadius(px, pz, epsilon, False, False, plhnd, -1, 0, False, False, False, False, False, False);
            if GetGameObjectListCount>1 then
            begin
               for j:=GetGameObjectListCount-1 downto 0 do
               begin
                  var objHnd : Integer = GetGameObjectListByIndex(j);
                  if (objHnd<>goHnd) and (GetGameObjectCollisionDetectionByHandle(objHnd)) and (VectorDistance(px, 0, pz, GetGameObjectPositionXByHandle(objHnd), 0, GetGameObjectPositionZByHandle(objHnd))<epsilon) then
                  begin
                     //Log('objHnd='+IntToStr(objHnd)+' bn='+GetGameObjectBaseNameByHandle(objHnd)+' x='+FloatToStr(GetGameObjectPositionXByHandle(objHnd))+' z='+FloatToStr(GetGameObjectPositionZByHandle(objHnd)));
                     bfound := True;
                     GameObjectDestroyByHandle(goHnd);
                     break;
                  end;
               end;
            end;
         end;
      end;
      if (not bfound) then
      break(MAIN);
   end;
end;

// _misc_RemoveEnvObjectsOutsideOfMap
//
procedure _misc_RemoveEnvObjectsOutsideOfMap();
begin
   if (GetCountOfPlayers>gc_playerind_env) then
   begin
      var hmw : Integer = GetMapWidth div 2;
      var hmh : Integer = GetMapHeight div 2;
      var plhnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
      var i : Integer;
      for i:=GetPlayerGameObjectsCountByHandle(plhnd)-1 downto 0 do
      begin
         var goHnd : Integer = GetGameObjectHandleByIndex(i, plhnd);
         var px : Float = GetGameObjectPositionXByHandle(goHnd);
         var pz : Float = GetGameObjectPositionZByHandle(goHnd);
         if (px<-hmw) or (px>hmw) or (pz<-hmh) or (pz>hmh) then
         GameObjectDestroyByHandle(goHnd);
      end;
   end;
end;

// _misc_FixEnvironmentObjectsScales
//
procedure _misc_FixEnvironmentObjectsScales(const bJustLogError : Boolean);
begin
   if (GetCountOfPlayers<gc_playerind_env) then
   exit;
   var i, j : Integer;
   var plHnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
   if (plHnd<>0) then
   for i:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
   begin
      var goHnd : Integer = GetGameObjectHandleByIndex(i, plHnd);
      if (GetGameObjectVisibleByHandle(goHnd)) then
      begin
         var pres : Pointer = _misc_GetObjectArgData(goHnd, gc_argenv_res);
         if (pres<>nil) and (TBaseObj(pres).baseid=gc_baseid_res) and ((TRes(pres).itype=gc_resource_type_none) or (TRes(pres).itype=gc_resource_type_wood)) then
         begin
            var basename : String = GetGameObjectBaseNameByHandle(goHnd);
            var scale : Float = GetGameObjectScaleXByHandle(goHnd);
            var testScaleMin : Float = 100;
            var testScaleMax : Float;
            var testScaleSum : Float;
            var testScaleMid : Float = 1;
            var trycount : Integer = 100;
            for j:=0 to trycount-1 do
            begin
               var testHnd : Integer = CreatePlayerGameObjectHandleByHandle(plHnd, gc_racename_env, basename, 0, 0, 0);
               var testScale : Float = GetGameObjectScaleXByHandle(testHnd);
               if (testScale<testScaleMin) then
               testScaleMin := testScale;
               if (testScale>testScaleMax) then
               testScaleMax := testScale;
               testScaleSum := testScaleSum+testScale;
               GameObjectDestroyByHandle(testHnd);
            end;
            testScaleMid := (testScaleSum/trycount);
            if ((Abs(testScaleMax-testScaleMin))>testScaleMid*0.05) then
            begin
               if (scale<testScaleMin) then
               begin
                  const cDiffKoefMin = 0.875;
                  const cDiffKoefMax = 0.95;
                  var koef : Float = (testScaleMin/testScaleMid)*(cDiffKoefMin+random*(cDiffKoefMax-cDiffKoefMin));
                  var newscale : Float = testScaleMid*koef;
                  if (newscale<scale) then
                  newscale := scale;
                  
                  if (bJustLogError) then
                  ErrorLog('wrong scale < : basename='+basename+' scale='+FloatToStr(scale)+' smin='+FloatToStr(testScaleMin)+' smax='+FloatToStr(testScaleMax)+' difscale='+FloatToStr(testScaleMid/testScaleMax)+' x='+FloatToStr(GetGameObjectPositionXByHandle(goHnd))+' z='+FloatToStr(GetGameObjectPositionZByHandle(goHnd)))
                  else
                  SetGameObjectScaleByHandle(goHnd, newscale, newscale, newscale);
               end
               else
               if (scale>testScaleMax) then
               begin
                  const cDiffKoefMin = 1.04;
                  const cDiffKoefMax = 1.08;
                  var koef : Float = (testScaleMax/testScaleMid)*(cDiffKoefMin+random*(cDiffKoefMax-cDiffKoefMin));
                  var newscale : Float = testScaleMid*koef;
                  if (newscale>scale) then
                  newscale := scale;
                  
                  if (bJustLogError) then
                  ErrorLog('wrong scale > : basename='+basename+' scale='+FloatToStr(scale)+' smin='+FloatToStr(testScaleMin)+' smax='+FloatToStr(testScaleMax)+' difscale='+FloatToStr(testScaleMid/testScaleMin)+' x='+FloatToStr(GetGameObjectPositionXByHandle(goHnd))+' z='+FloatToStr(GetGameObjectPositionZByHandle(goHnd)))
                  else
                  SetGameObjectScaleByHandle(goHnd, newscale, newscale, newscale);
               end;
            end;
         end;
      end;
   end;
end;

// _misc_GetFreePatternMaskCountModifier
//
function _misc_GetFreePatternMaskCountModifier(testsize : Integer) : Integer;
begin
   Result := 0;
   
   var mapwidth : Integer = GetMapWidth;
   var mapheight : Integer = GetMapHeight;
   var x0 : Integer = -(mapwidth div 2);
   var y0 : Integer = -(mapheight div 2);
   
   var mask : array [0..gc_terraindata_mapwidth-1] of array [0..gc_terraindata_mapheight-1] of Boolean;
   var i, j, x, y, ix, iy : Integer;
   
   var iTestSize : Integer;
   if (testsize>0) then
   iTestSize := testsize
   else
   iTestSize := 16;
   
   var trycount1 : Integer = 4;
   var trycount2 : Integer = 32000;
   for i:=0 to trycount1-1 do
   begin
      for y:=0 to gc_terraindata_mapheight-1 do
      for x:=0 to gc_terraindata_mapwidth-1 do
      mask[x, y] := gPatternMask[x, y];
      for j:=0 to trycount2-1 do
      begin
         var bBusy : Boolean;
         x := floor(RandomExt*(mapwidth-iTestSize));
         y := floor(RandomExt*(mapheight-iTestSize));
         for [MAIN]iy:=y to (y+iTestSize-1) do
         for ix:=x to (x+iTestSize-1) do
         begin
            if (mask[ix, iy]) then
            begin
               bBusy := True;
               break(MAIN);
            end;
         end;
         if (not bBusy) then
         begin
            Result := Result+1;
            for iy:=y to (y+iTestSize-1) do
            for ix:=x to (x+iTestSize-1) do
            mask[ix, iy] := True;
         end;
      end;
   end;
   Result := floor(Result/trycount1);
end;

// _misc_GetFreePatternMaskModifier
//
procedure _misc_GetFreePatternMaskModifier(var probsmall, probmid, problarge, probhuge : Float);
begin
   const cSmall = 340;
   const cMid = 182;
   const cLarge = 74;
   const cHuge = 55;
   probsmall := _misc_GetFreePatternMaskCountModifier(12)/cSmall;
   probmid := _misc_GetFreePatternMaskCountModifier(16)/cMid;
   problarge := _misc_GetFreePatternMaskCountModifier(24)/cLarge;
   probhuge := _misc_GetFreePatternMaskCountModifier(29)/cHuge;
end;

// _misc_GetUnitOrderList
//
procedure _misc_GetUnitOrderList(goHnd : Integer; var sOrdList : String);
begin
   var pObj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if pObj <> nil then
   begin
      sOrdList := '';
      var i : Integer;
      var count : Integer;
      for i := 0 to gc_obj_MaxOrderCount-1 do
      begin
         var ordType : Integer = TObj(pObj).orders[i].iType;
         if ordType <> gc_obj_order_type_none then
         begin
            if count = 0 then
            sOrdList := '('
            else
            sOrdList := sOrdList + ', ';
            
            var sOrd : String;
            _misc_GetUnitOrderTypeByIndex(ordType, sOrd);
            sOrdList := sOrdList + sOrd;
            
            count := count+1;
         end;
      end;
      
      if count > 0 then
      sOrdList := sOrdList + ')';
   end;
end;

// _misc_GetMaxPlayerDifficultyByMap
//
function _misc_GetMaxPlayerDifficultyByMap() : Integer;
begin
   Result := gc_player_difficulty_easy;
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   if (gMap.players[i].bexists) then
   Result := Max(Result, gMap.players[i].aidifficulty);
end;

// _misc_GetMaxPlayerDifficultyByPlayer
//
function _misc_GetMaxPlayerDifficultyByPlayer() : Integer;
begin
   Result := gc_player_difficulty_easy;
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   if (gPlayer[i].bexists) then
   Result := Max(Result, gPlayer[i].difficulty);
end;

//_misc_GetGateBaseSprite
//
function _misc_GetGateBaseSprite(const goHnd : Integer) : Integer;
begin
   Result := -1;
   var pCluster : Pointer;
   var plHnd : Integer = GetGameObjectPlayerHandleByHandle(goHnd);
   var plInd : Integer = GetPlayerIndexByHandle(plHnd);
   var index : Integer = gWallSystem.GetWallClusterByHandle(plInd, goHnd, pCluster);
   if (index>0) and (index<TWallCluster(pCluster).Cells.GetCount-1) then
   begin
      var p1 : Pointer = TWallCluster(pCluster).Cells.GetPointer(index-1);
      var p2 : Pointer = TWallCluster(pCluster).Cells.GetPointer(index);
      var p3 : Pointer = TWallCluster(pCluster).Cells.GetPointer(index+1);
      var goX : Float = GetGameObjectPositionXByHandle(goHnd);
      var goZ : Float = GetGameObjectPositionZByHandle(goHnd);
      
      if (TWallCell(p1).GetSprite=TWallCell(p2).GetSprite) and (TWallCell(p3).GetSprite=TWallCell(p2).GetSprite) and (TWallCell(p1).goHnd<>0) and (TWallCell(p2).goHnd<>0) and (TWallCell(p3).goHnd<>0) then
      begin
         var pObj1 : Pointer = _misc_GetObjectArgData(TWallCell(p1).goHnd, gc_argunit_obj);
         var pObj2 : Pointer = _misc_GetObjectArgData(TWallCell(p2).goHnd, gc_argunit_obj);
         var pObj3 : Pointer = _misc_GetObjectArgData(TWallCell(p3).goHnd, gc_argunit_obj);
         
         if (pObj1<>nil) and (pObj2<>nil) and (pObj3<>nil) and (TObj(pObj1).bbuilt) and (TObj(pObj2).bbuilt) and (TObj(pObj3).bbuilt) then
         begin
            const rad : Float = 1.85;
            _misc_MakeListObjectsInRadius(goX, goZ, rad, true, (1 shl gc_obj_material_building) or (1 shl gc_obj_material_woodwall), 0, True, gIntegerList);
            var numWalls : Integer;
            var i : Integer;
            for i:=0 to gIntegerList.GetCount-1 do
            begin
               var objHnd : Integer = gIntegerList.Get(i);
               var pObj : Pointer = _misc_GetObjectArgData(objHnd, gc_argunit_obj);
               if (pObj<>nil) then
               begin
                  var usage : Integer = gObjProp[TObj(pobj).cid][TObj(pobj).id].usage;
                  if (usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall) then
                  numWalls := numWalls+1;
                  if (numWalls>3) then
                  break;
               end;
            end;
            if (numWalls=3) then
            begin
               var newSprite : Integer;
               //0 : sid := 'turswa_02'; //15
               //1 : sid := 'turswa_03'; //16
               //2 : sid := 'turswa_01'; //14
               //3 : sid := 'turswa_04'; //17
               case TWallCell(p1).GetSprite of
                  0 : newSprite := 15;
                  1 : newSprite := 16;
                  2 : newSprite := 14;
                  3 : newSprite := 17;
               end;
               Result := newSprite;
            end;
         end;
      end;
   end;
end;

// _misc_GetWallVariationByName
//
function _misc_GetWallVariationByName(const name : String) : Integer;
begin
   Result := 0;
   var s0 : String = SubStr(name, StrLength(name)-1, 1);
   var s1 : String = SubStr(name, StrLength(name), 1);
   if (s0='0') then Result := StrToInt(s1)
   else
   Result := StrToInt(s0+s1);
   if (Result<1) or (Result>17) then
   begin
      Result := 0;
      ErrorLog('_unit_GetFreeBuilderPoint : unknown wall variation name='+name);
   end
end;

//_misc_GetSpriteBySID
//
function _misc_GetSpriteBySID(sid : String) : Integer;
begin
   const cid_aus = 0;
   const cid_fra = 1;
   const cid_eng = 2;
   const cid_spa = 3;
   const cid_rus = 4;
   const cid_ukr = 5;
   const cid_pol = 6;
   const cid_swe = 7;
   const cid_pru = 8;
   const cid_ven = 9;
   const cid_tur = 10;
   const cid_alg = 11;
   const cid_mis = 12;
   const cid_net = 13;
   const cid_den = 14;
   const cid_por = 15;
   const cid_pie = 16;
   const cid_sax = 17;
   const cid_bav = 18;
   const cid_hun = 19;
   const cid_swi = 20;
   const cid_sco = 21;
   const cid_tat = 22;
   const cid_lit = 23;
   
   Result := -1;
   var pos : Integer = StrPos('_', sid);
   if (pos>0) then
   begin
      var suffix : String = SubStr(sid, pos+1, 2);
      case suffix of
         '02' : Result := 0; //15
         '03' : Result := 1; //16
         '01' : Result := 2; //14
         '04' : Result := 3; //17
         '13' : Result := 4; //09
         '11' : Result := 5;
         '07' : Result := 6;
         '09' : Result := 7;
         '10' : Result := 8;
         '12' : Result := 9;
         '08' : Result := 10;
         '06' : Result := 11;
         '05' : Result := 12;
         //gates
         '14' : Result := 14;
         '15' : Result := 15;
         '16' : Result := 16;
         '17' : Result := 17;
      end;
   end;
end;

//_misc_GetSIDBySprite
//
procedure _misc_GetSIDBySprite(usage, sprite, cid : Integer; var sid : string);
begin
   const cid_aus = 0;
   const cid_fra = 1;
   const cid_eng = 2;
   const cid_spa = 3;
   const cid_rus = 4;
   const cid_ukr = 5;
   const cid_pol = 6;
   const cid_swe = 7;
   const cid_pru = 8;
   const cid_ven = 9;
   const cid_tur = 10;
   const cid_alg = 11;
   const cid_mis = 12;
   const cid_net = 13;
   const cid_den = 14;
   const cid_por = 15;
   const cid_pie = 16;
   const cid_sax = 17;
   const cid_bav = 18;
   const cid_hun = 19;
   const cid_swi = 20;
   const cid_sco = 21;
   const cid_tat = 22;
   const cid_lit = 23;
   
   //var index : Integer;
   //var usage : Integer = wallcluster.wallType;
   if (usage=gc_obj_usage_hardwall) then
   begin
      if (cid=cid_alg) or (cid=cid_tur) then
      begin
         case sprite of
            0 : sid := 'turswa_02'; //15
            1 : sid := 'turswa_03'; //16
            2 : sid := 'turswa_01'; //14
            3 : sid := 'turswa_04'; //17
            4 : sid := 'turswa_13';     //09
            5 : sid := 'turswa_11';
            6 : sid := 'turswa_07';
            7 : sid := 'turswa_09';
            8 : sid := 'turswa_10';
            9 : sid := 'turswa_12';
            10 : sid := 'turswa_08';
            11 : sid := 'turswa_06';
            12 : sid := 'turswa_05';
            //gates
            14 : sid := 'tursga_14';
            15 : sid := 'tursga_15';
            16 : sid := 'tursga_16';
            17 : sid := 'tursga_17';
         end;
      end
      else if (cid=cid_rus) then
      begin
         case sprite of
            0 : sid := 'russwa_02';
            1 : sid := 'russwa_03';
            2 : sid := 'russwa_01';
            3 : sid := 'russwa_04';
            4 : sid := 'russwa_13';     //09
            5 : sid := 'russwa_11';
            6 : sid := 'russwa_07';
            7 : sid := 'russwa_09';
            8 : sid := 'russwa_10';
            9 : sid := 'russwa_12';
            10 : sid := 'russwa_08';
            11 : sid := 'russwa_06';
            12 : sid := 'russwa_05';
            //gates
            14 : sid := 'russga_14';
            15 : sid := 'russga_15';
            16 : sid := 'russga_16';
            17 : sid := 'russga_17';
         end;
      end
      else
      begin
         case sprite of
            0 : sid := 'eurswa_02';
            1 : sid := 'eurswa_03';
            2 : sid := 'eurswa_01';
            3 : sid := 'eurswa_04';
            4 : sid := 'eurswa_13';     //09
            5 : sid := 'eurswa_11';
            6 : sid := 'eurswa_07';
            7 : sid := 'eurswa_09';
            8 : sid := 'eurswa_10';
            9 : sid := 'eurswa_12';
            10 : sid := 'eurswa_08';
            11 : sid := 'eurswa_06';
            12 : sid := 'eurswa_05';
            //gates
            14 : sid := 'eursga_14';
            15 : sid := 'eursga_15';
            16 : sid := 'eursga_16';
            17 : sid := 'eursga_17';
         end;
      end
   end
   else
   begin
      case sprite of
         0 : sid := 'ukrwwa_02';
         1 : sid := 'ukrwwa_03';
         2 : sid := 'ukrwwa_01';
         3 : sid := 'ukrwwa_04';
         4 : sid := 'ukrwwa_13';     //09
         5 : sid := 'ukrwwa_11';
         6 : sid := 'ukrwwa_07';
         7 : sid := 'ukrwwa_09';
         8 : sid := 'ukrwwa_10';
         9 : sid := 'ukrwwa_12';
         10 : sid := 'ukrwwa_08';
         11 : sid := 'ukrwwa_06';
         12 : sid := 'ukrwwa_05';
         //gates
         14 : sid := 'ukrwga_14';
         15 : sid := 'ukrwga_15';
         16 : sid := 'ukrwga_16';
         17 : sid := 'ukrwga_17';
      end;
   end;
end;

// _misc_GetPeaceTime
//
function _misc_GetPeaceTime(ind : Integer) : Float;
begin
   case ind of
      gc_mapsettings_peacetime_default : Result := 0;
      gc_mapsettings_peacetime_10: Result := 10;
      gc_mapsettings_peacetime_20: Result := 20;
      gc_mapsettings_peacetime_30: Result := 30;
      gc_mapsettings_peacetime_45: Result := 45;
      gc_mapsettings_peacetime_60: Result := 60;
      gc_mapsettings_peacetime_90: Result := 90;
      gc_mapsettings_peacetime_120: Result := 120;
      gc_mapsettings_peacetime_180: Result := 180;
      gc_mapsettings_peacetime_240: Result := 240;
      gc_mapsettings_peacetime_15: Result := 15;
      else
      Result := 0;
   end;
   Result := Result * 60;
end;

// _misc_PlaceFieldDecal
//
procedure _misc_PlaceFieldDecal(goHnd : Integer; scale, angle : Float);
begin
   const decalname = 'envfieldd';
   var rollangle : Float = {GetGameObjectRollAngleByHandle(goHnd)+}angle;
   var w, h, x, z, texOff0, texOff1, scale0, scale1, texScale0, texScale1, texAng, eps : Float;
   var texRot, texScale, texOff, texOffRot : Boolean;
   var libMat, decName : String;
   var goScale : Float = GetGameObjectScaleXByHandle(goHnd);
   var lib, name : String = decalname;
   var decHnd : Integer = AddNewDecalByLibName(goHnd, x, z, rollangle, lib, name);
   GetDecalDataByHandle(decHnd, w, h, x, z, texOff0, texOff1, scale0, scale1, texScale0, texScale1, texAng, eps, texRot, texScale, texOff, texOffRot, libMat, decName);
   
   const texscalefix = 1.3;
   w := scale*goScale*texscalefix;
   h := scale*goScale*texscalefix;
   texscale0 := texscale0*texscalefix;
   texscale1 := texscale1*texscalefix;
   texOff0 := -(texscalefix-1)/2;
   texOff1 := -(texscalefix-1)/2;
   SetDecalDataByHandle(decHnd, w, h, x, z, texOff0, texOff1, scale0, scale1, texScale0, texScale1, texAng, eps, texRot, texScale, texOff, texOffRot, libMat, decName);
end;

// _player_IsBuildingInRadius
//
function _player_IsBuildingInRadius(const buildHnd, plInd : Integer; const x, y, rad : Float) : Boolean;
begin
   Result := False;
   _misc_MakeListObjectsInRadius(x, y, rad, True, (1 shl gc_obj_material_building), 0, True, gIntegerList);
   var i : Integer;
   for i:=gIntegerList.GetCount-1 downto 0 do
   begin
      var goHnd : Integer = gIntegerList.Get(i);
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) {and (TObj(pobj).bbuilt)} then
      begin
         Result := True;
         break;
      end;
   end;
end;

// _misc_IsCollisionExists
//
function _misc_IsCollisionExists(x, y : Float) : Boolean;
begin
   Result := False;
   const mincount = 4;
   var col, tag : Integer;
   var i, j : Integer;
   var arrx : Integer = floor(x+gc_MaxMapWidth div 2);
   var arry : Integer = floor(y+gc_MaxMapHeight div 2);
   if (not gCanPlaceBuildingWalls[arrx][arry].bcollisionchecked) then
   begin
      gCanPlaceBuildingWalls[arrx][arry].bcollision := False;
      gCanPlaceBuildingWalls[arrx][arry].bcollisionchecked := True;
      for [MAIN]i:=0 to 1 do
      for j:=0 to 1 do
      begin
         tag := GetMapCollisionTagInRadius(x+i*0.5-0.25, y+j*0.5-0.25, 0, true);
         if (tag>0) and (tag<>gc_collisiontag_wall) and (tag<>gc_collisiontag_building) then // check 1 tile collision
         begin
            col := col+1;
            if (col>=mincount) then
            begin
               Result := True;
               gCanPlaceBuildingWalls[arrx][arry].bcollision := True;
               exit;
            end;
         end;
      end;
   end
   else
   Result := gCanPlaceBuildingWalls[arrx][arry].bcollision;
end;

// _misc_IsEnemyWallExists
//
function _misc_IsEnemyWallExists(pl : Integer; x, y : Float) : Boolean;
begin
   Result := False;
   const cEpsilon = 0.125;
   var px : Float = floor(x)+0.5;
   var py : Float = floor(y)+0.5;
   _misc_MakeListObjectsInRadius(px, py, cEpsilon, true, (1 shl gc_obj_material_building) or (1 shl gc_obj_material_woodwall), 0, False, gIntegerList);
   var i : Integer;
   for i:=0 to gIntegerList.GetCount-1 do
   begin
      var hnd : Integer = gIntegerList.Get(i);
      var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
      if (pobj<>nil) then
      begin
         var usage : Integer = gObjProp[TObj(pobj).cid][TObj(pobj).id].usage;
         if (TObj(pobj).pl<>pl) and ((usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall)) and (not gObjProp[TObj(pobj).cid][TObj(pobj).id].bgate) then
         begin
            Result := True;
            exit;
         end;
      end;
   end;
end;

// _misc_IsPlayerWallExists
//
function _misc_IsPlayerWallExists(pl : Integer; x, y : Float) : Boolean;
begin
   Result := False;
   var arrx : Integer = floor(x+gc_MaxMapWidth div 2);
   var arry : Integer = floor(y+gc_MaxMapHeight div 2);
   if (not gCanPlaceBuildingWalls[arrx][arry].bwallchecked) then
   begin
      gCanPlaceBuildingWalls[arrx][arry].bwall := False;
      gCanPlaceBuildingWalls[arrx][arry].bwallchecked := True;
      gCanPlaceBuildingWalls[arrx][arry].wallsprite := -1;
      const cEpsilon = 0.125;
      var i : Integer;
      var px : Float = floor(x)+0.5;
      var py : Float = floor(y)+0.5;
      var miscplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
      if (miscplhnd<>0) then
      begin
         GetGameObjectsInRadius(px, py, cEpsilon, false, true, miscplhnd, -1, 0, false, false, false, false, false, false);
         for i:=GetGameObjectListCount-1 downto 0 do
         begin
            var hnd : Integer = GetGameObjectListByIndex(i);
            var ax : Float = GetGameObjectPositionXByHandle(hnd);
            var ay : Float = GetGameObjectPositionZByHandle(hnd);
            if (VectorDistance(px, 0, py, ax, 0, ay)<cEpsilon) then
            begin
               gCanPlaceBuildingWalls[arrx][arry].bwall := True;
               gCanPlaceBuildingWalls[arrx][arry].wallsprite := _misc_GetSpriteBySID(GetGameObjectBaseNameByHandle(hnd));
               Result := True;
               break;
            end;
         end;
      end;
      _misc_MakeListObjectsInRadius(px, py, cEpsilon, true, (1 shl gc_obj_material_building) or (1 shl gc_obj_material_woodwall), 0, False, gIntegerList);
      for i:=0 to gIntegerList.GetCount-1 do
      begin
         var hnd : Integer = gIntegerList.Get(i);
         var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            var usage : Integer = gObjProp[TObj(pobj).cid][TObj(pobj).id].usage;
            if ((usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall)) and (not gObjProp[TObj(pobj).cid][TObj(pobj).id].bgate) then
            begin
               //if (TObj(pobj).pl=pl) then // this condition no more needed
               //begin
                  gCanPlaceBuildingWalls[arrx][arry].bwall := True;
                  gCanPlaceBuildingWalls[arrx][arry].wallsprite := _misc_GetSpriteBySID(GetGameObjectBaseNameByHandle(hnd));
                  Result := True;
               //end
            end;
         end;
      end;
   end
   else
   Result := gCanPlaceBuildingWalls[arrx][arry].bwall;
end;

// _misc_IsPlayerWallDiagonal
//
function _misc_IsPlayerWallDiagonal(pl : Integer; x, y : Float) : Boolean;
begin
   Result := False;
   var arrx : Integer = floor(x+gc_MaxMapWidth div 2);
   var arry : Integer = floor(y+gc_MaxMapHeight div 2);
   if (not gCanPlaceBuildingWalls[arrx][arry].bwallchecked) then
   begin
      gCanPlaceBuildingWalls[arrx][arry].bwall := False;
      gCanPlaceBuildingWalls[arrx][arry].bwallchecked := True;
      gCanPlaceBuildingWalls[arrx][arry].wallsprite := -1;
      const cEpsilon = 0.125;
      var i : Integer;
      var px : Float = floor(x)+0.5;
      var py : Float = floor(y)+0.5;
      var miscplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
      if (miscplhnd<>0) then
      begin
         GetGameObjectsInRadius(px, py, cEpsilon, false, true, miscplhnd, -1, 0, false, false, false, false, false, false);
         for i:=GetGameObjectListCount-1 downto 0 do
         begin
            var hnd : Integer = GetGameObjectListByIndex(i);
            var ax : Float = GetGameObjectPositionXByHandle(hnd);
            var ay : Float = GetGameObjectPositionZByHandle(hnd);
            if (VectorDistance(px, 0, py, ax, 0, ay)<cEpsilon) {and (True or (pl<0) or (pl=GetPlayerIndexByHandle(GetGameObjectPlayerHandleByHandle(hnd))))} then
            begin
               gCanPlaceBuildingWalls[arrx][arry].bwall := True;
               gCanPlaceBuildingWalls[arrx][arry].wallsprite := _misc_GetSpriteBySID(GetGameObjectBaseNameByHandle(hnd));
               Result := (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>0) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>2) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>12);
               break;
            end;
         end;
      end;
      _misc_MakeListObjectsInRadius(px, py, cEpsilon, true, (1 shl gc_obj_material_building) or (1 shl gc_obj_material_woodwall), 0, False, gIntegerList);
      for i:=0 to gIntegerList.GetCount-1 do
      begin
         var hnd : Integer = gIntegerList.Get(i);
         var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            var usage : Integer = gObjProp[TObj(pobj).cid][TObj(pobj).id].usage;
            if ((usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall)) and (not gObjProp[TObj(pobj).cid][TObj(pobj).id].bgate) then
            begin
               //if (pl<0) or (pl=TObj(pobj).pl) then // condition not needed any more
               //begin
                  gCanPlaceBuildingWalls[arrx][arry].bwall := True;
                  gCanPlaceBuildingWalls[arrx][arry].wallsprite := _misc_GetSpriteBySID(GetGameObjectBaseNameByHandle(hnd));
                  Result := (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>0) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>2) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>12);
                  exit;
               //end;
            end;
         end;
      end;
   end
   else
   Result := (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>0) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>2) and (gCanPlaceBuildingWalls[arrx][arry].wallsprite<>12);
end;

//_misc_GetPortResourcePoint
//
function _misc_GetPortResourcePoint(const goHnd : Integer; var x, z : Float; bPrecise : Boolean) : Boolean;
begin
   Result := False;
   var bui : Integer = GetGameObjectGOHandleChildByBaseName(goHnd, gc_racename_misc, gc_basename_bui);
   if (bui<>0) then
   begin
      var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
      if (pobj<>nil) and (TObj(pobj).bbuilt) then
      begin
         var y : Float;
         GetGameObjectAbsolutePositionByHandle(bui, x, y, z);
         Result := True;
      end;
   end;
   if (not Result) then
   begin
      Result := False;
      var portX : Float = GetGameObjectPositionXByHandle(goHnd);
      var portZ : Float = GetGameObjectPositionZByHandle(goHnd);
      var minDistance : Float = gc_MaxFloat;
      var minIndex : Integer = -1;
      var minDist : Float = 9;
      var maxDist : Float = 11;
      var i, j, tries : Integer;
      if (bPrecise) then
      begin
         minDist := minDist-6;
         const cPresiseCalc = 72;
         tries := cPresiseCalc;
         var fDistance : array [0..cPresiseCalc-1] of Float;
         for i:=0 to tries-1 do
         begin
            const cCollisionRad = 1;
            var angle : Float = i*(360/tries);
            var dirX : Float = maxDist;
            var dirY, dirZ : Float;
            VectorRotateY(dirX, dirY, dirZ, angle);
            var coltag : Integer = GetMapCollisionTagInRadius(portX+dirX, portZ+dirZ, cCollisionRad, True);
            if (coltag=gc_collisiontag_water) then
            begin
               fDistance[i] := maxDist;
               for j:=floor(minDist) to floor(maxDist)-1 do
               begin
                  var tmpX : Float = j;
                  var tmpY, tmpZ : Float;
                  VectorRotateY(tmpX, tmpY, tmpZ, angle);
                  coltag := GetMapCollisionTagInRadius(portX+tmpX, portZ+tmpZ, cCollisionRad, True);
                  if (coltag=gc_collisiontag_water) then
                  begin
                     fDistance[i] := j;
                     case i of
                        84/angle..96/angle : fDistance[i] := fDistance[i]*1.1;
                        174/angle..186/angle : fDistance[i] := fDistance[i]*1.1;
                        264/angle..276/angle : fDistance[i] := fDistance[i]*1.1;
                        354/angle..366/angle : fDistance[i] := fDistance[i]*1.1;
                        0/angle..6/angle : fDistance[i] := fDistance[i]*1.1;
                     end;
                     break;
                  end;
               end;
            end;
         end;
         for i:=0 to tries-1 do
         begin
            if (fDistance[i]>0) and (fDistance[i]<minDistance) then
            begin
               minDistance := fDistance[i];
               minIndex := i;
            end;
         end;
      end
      else
      begin
         const cFastCalc = 16;
         tries := cFastCalc;
         var fDistance : array [0..cFastCalc-1] of Float;
         for i:=0 to tries-1 do
         begin
            const cCollisionRad = 1;
            var angle : Float = i*(360/tries);
            var dirX : Float = maxDist;
            var dirY, dirZ : Float;
            VectorRotateY(dirX, dirY, dirZ, angle);
            var coltag : Integer = GetMapCollisionTagInRadius(portX+dirX, portZ+dirZ, cCollisionRad, True);
            if (coltag=gc_collisiontag_water) then
            begin
               fDistance[i] := maxDist;
               for j:=floor(minDist) to floor(maxDist)-1 do
               begin
                  var tmpX : Float = j;
                  var tmpY, tmpZ : Float;
                  VectorRotateY(tmpX, tmpY, tmpZ, angle);
                  coltag := GetMapCollisionTagInRadius(portX+tmpX, portZ+tmpZ, cCollisionRad, True);
                  if (coltag=gc_collisiontag_water) then
                  begin
                     fDistance[i] := j;
                     case i of
                        84/angle..96/angle : fDistance[i] := fDistance[i]*1.1;
                        174/angle..186/angle : fDistance[i] := fDistance[i]*1.1;
                        264/angle..276/angle : fDistance[i] := fDistance[i]*1.1;
                        354/angle..366/angle : fDistance[i] := fDistance[i]*1.1;
                        0/angle..6/angle : fDistance[i] := fDistance[i]*1.1;
                     end;
                     break;
                  end;
               end;
            end;
         end;
         for i:=0 to tries-1 do
         begin
            if (fDistance[i]>0) and (fDistance[i]<minDistance) then
            begin
               minDistance := fDistance[i];
               minIndex := i;
            end;
         end;
      end;
      if (minIndex>=0) then
      begin
         var angle : Float = minIndex*5;
         if (minDistance<7.5) then
         minDistance := 7.5;
         var dirX : Float = minDistance;
         var dirY, dirZ : Float;
         VectorRotateY(dirX, dirY, dirZ, angle);
         x := portX+dirX;
         z := portZ+dirZ;
         Result := True;
      end;
   end;
end;

// _misc_CanPlaceBuilding
//
function _misc_CanPlaceBuilding(const buildHnd, plInd, usage : Integer; const x, y, colRad, unitRad, envRad, mineRad : Float; bPrecise : Boolean) : Boolean;
begin
   _misc_ProfilerBegin('_misc_CanPlaceBuilding');
   Result := true;
   
   const cHeightDelta = 0.275;
   var w : Integer = GetGameObjectCollisionMaskWidthByHandle(buildHnd);
   var h : Integer = GetGameObjectCollisionMaskHeightByHandle(buildHnd);
   var mapW : Integer = GetMapWidth;
   var mapH : Integer = GetMapHeight;
   var i, j, k : Integer;
   var rad : Integer = Round(2*colRad);
   var minheight : Float = 10000;
   var maxheight : Float = -10000;
   
   var nearBuild : Boolean;
   var bwall : Boolean = (usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall);
   var bport : Boolean = (usage=gc_obj_usage_port);
   if True or (usage=gc_obj_usage_tower) or (bwall) or (bport) then
   nearBuild := True
   else
   nearBuild := _player_IsBuildingInRadius(buildHnd, plInd, x, y, gc_building_ignoretreesradius);
   var renvminerad : Float = _misc_MaxFloat(envrad, minerad);
   var bDoScan : Boolean = (envrad>=0) or (unitrad>=0) or (minerad>=0);
   var tries : Integer;
   if (bport) then
   begin
      var tempx, tempz : Float;
      if (not _misc_GetPortResourcePoint(buildHnd, tempx, tempz, bPrecise)) then
      Result := False;
   end
   else
   if (bwall) then
   begin
      var bok : Boolean;
      for [MAIN]i:=-1 to 0 do
      for j:=-1 to 0 do
      if (GetMapCollisionTagInRadius(x+i*0.5+0.25, y+j*0.5+0.25, 0, true)=0) then // check 1 tile collision
      begin
         bok := True;
         break(MAIN);
      end;
      var cmaxh, cmaxw, ch, cw : Integer;
      var cminh, cminw : Integer = gc_MaxInt;
      for i := 0 to h-1 do
      for j := 0 to w-1 do
      if GetGameObjectCollisionMaskValueByHandle(buildHnd, i, j) then
      begin
         cminw := Min(cminw, i);
         cminh := Min(cminh, j);
         cmaxw := Max(cmaxw, i);
         cmaxh := Max(cmaxh, j);
      end;
      cw := cmaxw-cminw+1;
      ch := cmaxh-cminh+1;
      var margin : Integer = (Max(cw, ch)-2) div 2;
      if (Max(cw, ch) mod 2>0) then
      margin := margin+1;
      for [MAIN]i:=-4-margin to 3+margin do
      for j:=-4-margin to 3+margin do
      begin
         if (margin=1) then
         begin
            if ((Abs(i)+Abs(j))>7) then
            continue;
         end;
         if (GetMapCollisionTagInRadius(x+i*0.5+0.25, y+j*0.5+0.25, 0, true)=gc_collisiontag_building) then
         begin
            bok := False;
            break(MAIN);
         end;
      end;
      if (not bok) then
      begin
         Result := false;
         exit;
      end;
      
      var bwallallow, bcolallow : Boolean;
      if (Result) then
      begin
         Result := True;
         var itry : Integer;
         
         for [MAIN]i:=-2 to 2 do
         for j:=-2 to 2 do
         begin
            var dst : Float = (Abs(i)+Abs(j));
            if (dst>=1) and (_misc_IsEnemyWallExists(plInd, x+i, y+j)) then
            begin
               Result := False;
               exit;
            end;
         end;
         
         if (Result) then
         for [MAIN]i:=-2 to 2 do
         for j:=-2 to 2 do
         begin
            var dst : Float = (Abs(i)+Abs(j));
            if (dst>=1) then
            begin
               if _misc_IsPlayerWallExists(plInd, x+i, y+j) then
               begin
                  var bAllowed : Boolean;
                  var idir : Integer = round(_misc_GetAngleToXVector(0, 0, i, j)/30);
                  case floor(dst) of
                     1 : bAllowed := True;
                     2 : begin
                        case idir of
                           0  : bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y);
                           -2 : bAllowed := (_misc_IsPlayerWallDiagonal(plInd, x+1, y+1)) or ((_misc_IsPlayerWallExists(plInd, x+1, y) or (_misc_IsPlayerWallExists(plInd, x, y+1))));
                           -3 : bAllowed := _misc_IsPlayerWallExists(plInd, x, y+1);
                           -4 : bAllowed := (_misc_IsPlayerWallDiagonal(plInd, x-1, y+1)) or ((_misc_IsPlayerWallExists(plInd, x-1, y) or (_misc_IsPlayerWallExists(plInd, x, y+1))));
                           6  : bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y);
                           4  : bAllowed := (_misc_IsPlayerWallDiagonal(plInd, x-1, y-1)) or ((_misc_IsPlayerWallExists(plInd, x-1, y) or (_misc_IsPlayerWallExists(plInd, x, y-1))));
                           3  : bAllowed := _misc_IsPlayerWallExists(plInd, x, y-1);
                           2  : bAllowed := (_misc_IsPlayerWallDiagonal(plInd, x+1, y-1)) or ((_misc_IsPlayerWallExists(plInd, x+1, y) or (_misc_IsPlayerWallExists(plInd, x, y-1))));
                        end;
                     end;
                     3 : begin
                        case idir of
                           -1 : bAllowed := (_misc_IsPlayerWallExists(plInd, x+1, y)) or (_misc_IsPlayerWallExists(plInd, x+1, y+1));
                           -2 : bAllowed := (_misc_IsPlayerWallExists(plInd, x+1, y+1)) or (_misc_IsPlayerWallExists(plInd, x, y+1));
                           -4 : bAllowed := (_misc_IsPlayerWallExists(plInd, x, y+1)) or (_misc_IsPlayerWallExists(plInd, x-1, y+1));
                           -5 : bAllowed := (_misc_IsPlayerWallExists(plInd, x-1, y+1)) or (_misc_IsPlayerWallExists(plInd, x-1, y));
                           5  : bAllowed := (_misc_IsPlayerWallExists(plInd, x-1, y)) or (_misc_IsPlayerWallExists(plInd, x-1, y-1));
                           4  : bAllowed := (_misc_IsPlayerWallExists(plInd, x-1, y-1)) or (_misc_IsPlayerWallExists(plInd, x, y-1));
                           2  : bAllowed := (_misc_IsPlayerWallExists(plInd, x, y-1)) or (_misc_IsPlayerWallExists(plInd, x+1, y-1));
                           1  : bAllowed := (_misc_IsPlayerWallExists(plInd, x+1, y-1)) or (_misc_IsPlayerWallExists(plInd, x+1, y));
                        end;
                     end;
                     4 : begin
                        case idir of
                           -2 : bAllowed := (_misc_IsPlayerWallExists(plInd, x+1, y+1)) or (_misc_IsPlayerWallExists(plInd, x+1, y)) or (_misc_IsPlayerWallExists(plInd, x, y+1));
                           -4 : bAllowed := (_misc_IsPlayerWallExists(plInd, x-1, y+1)) or (_misc_IsPlayerWallExists(plInd, x-1, y)) or (_misc_IsPlayerWallExists(plInd, x, y+1));
                           4  : bAllowed := (_misc_IsPlayerWallExists(plInd, x-1, y-1)) or (_misc_IsPlayerWallExists(plInd, x-1, y)) or (_misc_IsPlayerWallExists(plInd, x, y-1));
                           2  : bAllowed := (_misc_IsPlayerWallExists(plInd, x+1, y-1)) or (_misc_IsPlayerWallExists(plInd, x+1, y)) or (_misc_IsPlayerWallExists(plInd, x, y-1));
                        end;
                     end;
                  end;
                  if (not bAllowed) then
                  begin
                     bwallallow := False;
                     Result := False;
                     break(MAIN);
                  end
                  else
                  begin
                     bwallallow := True;
                     Result := True;
                  end;
               end;
            end;
         end;
         
         if (Result) then
         begin
            var bAllowed : Boolean;
            for [MAIN]itry:=1 to 4 do
            for i:= -2 to 2 do
            for j:= -2 to 2 do
            begin
               var dst : Float = (Abs(i)+Abs(j));
               if (dst>=1) and (dst=itry) and (not bAllowed) then
               begin
                  if (_misc_IsCollisionExists(x+i, y+j)) then
                  begin
                     var idir : Integer = round(_misc_GetAngleToXVector(0, 0, i, j)/30);
                     case floor(dst) of
                        1 : begin
                           case idir of
                              0  : bAllowed := _misc_IsCollisionExists(x+1, y);
                              -2 : bAllowed := True;
                              -3 : bAllowed := _misc_IsCollisionExists(x, y+1);
                              -4 : bAllowed := True;
                              6  : bAllowed := _misc_IsCollisionExists(x-1, y);
                              4  : bAllowed := True;
                              3  : bAllowed := _misc_IsCollisionExists(x, y-1);
                              2  : bAllowed := True;
                           end;
                        end;
                        2 : begin
                           {case idir of
                              0  : bAllowed := _misc_IsCollisionExists(x+1, y) or _misc_IsCollisionExists(x, y-1) or _misc_IsCollisionExists(x, y+1);
                              -2 : bAllowed := True;
                              -3 : bAllowed := bAllowed or _misc_IsCollisionExists(x, y+1);
                              -4 : bAllowed := True;
                              6  : bAllowed := _misc_IsCollisionExists(x-1, y);
                              4  : bAllowed := True;
                              3  : bAllowed := _misc_IsCollisionExists(x, y-1) or _misc_IsCollisionExists(x+1, y);
                              2  : bAllowed := True;
                           end;}
                        end;
                        3 : begin
                           case idir of
                              -1 : bAllowed := (_misc_IsCollisionExists(x+1, y)) or (_misc_IsCollisionExists(x+1, y+1));
                              -2 : bAllowed := (_misc_IsCollisionExists(x+1, y+1)) or (_misc_IsCollisionExists(x, y+1));
                              -4 : bAllowed := (_misc_IsCollisionExists(x, y+1)) or (_misc_IsCollisionExists(x-1, y+1));
                              -5 : bAllowed := (_misc_IsCollisionExists(x-1, y+1)) or (_misc_IsCollisionExists(x-1, y));
                              5  : bAllowed := (_misc_IsCollisionExists(x-1, y)) or (_misc_IsCollisionExists(x-1, y-1));
                              4  : bAllowed := (_misc_IsCollisionExists(x-1, y-1)) or (_misc_IsCollisionExists(x, y-1));
                              2  : bAllowed := (_misc_IsCollisionExists(x, y-1)) or (_misc_IsCollisionExists(x+1, y-1));
                              1  : bAllowed := bAllowed or (_misc_IsCollisionExists(x+1, y-1)) or (_misc_IsCollisionExists(x+1, y));
                           end;
                        end;
                        4 : begin
                           case idir of
                              -2 : bAllowed := (_misc_IsCollisionExists(x+1, y+1)) or (_misc_IsCollisionExists(x+1, y)) or (_misc_IsCollisionExists(x, y+1));
                              -4 : bAllowed := (_misc_IsCollisionExists(x-1, y+1)) or (_misc_IsCollisionExists(x-1, y)) or (_misc_IsCollisionExists(x, y+1));
                              4  : bAllowed := (_misc_IsCollisionExists(x-1, y-1)) or (_misc_IsCollisionExists(x-1, y)) or (_misc_IsCollisionExists(x, y-1));
                              2  : bAllowed := (_misc_IsCollisionExists(x+1, y-1)) or (_misc_IsCollisionExists(x+1, y)) or (_misc_IsCollisionExists(x, y-1));
                           end;
                        end;
                     end;
                     if (not bAllowed) then
                     begin
                        bcolallow := False;
                        Result := False;
                        break(MAIN);
                     end
                     else
                     begin
                        bcolallow := True;
                        Result := True;
                     end;
                  end;
               end;
            end;
         end;
         
         if (not Result) then
         begin
            var bAllowed : Boolean;
            var bChecked : Boolean;
            if (_misc_IsCollisionExists(x-1, y-1)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y) or _misc_IsPlayerWallExists(plInd, x, y-1);
               bChecked := True;
            end;
            if (_misc_IsCollisionExists(x+1, y-1)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y) or _misc_IsPlayerWallExists(plInd, x, y-1);
               bChecked := True;
            end;
            if (_misc_IsCollisionExists(x-1, y+1)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y) or _misc_IsPlayerWallExists(plInd, x, y-1);
               bChecked := True;
            end;
            if (_misc_IsCollisionExists(x+1, y+1)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y) or _misc_IsPlayerWallExists(plInd, x, y+1);
               bChecked := True;
            end;
            if (not bChecked) and (_misc_IsCollisionExists(x+2, y)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y);
               bChecked := True;
            end;
            if (not bChecked) and (_misc_IsCollisionExists(x-2, y)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y);
               bChecked := True;
            end;
            if (not bChecked) and (_misc_IsCollisionExists(x, y+2)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x, y+1);
               bChecked := True;
            end;
            if (not bChecked) and (_misc_IsCollisionExists(x, y-2)) then
            begin
               bAllowed := _misc_IsPlayerWallExists(plInd, x, y-1);
               bChecked := True;
            end;
            if (not bAllowed) and (_misc_IsCollisionExists(x-2, y-2)) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y-1);
            if (not bAllowed) and (_misc_IsCollisionExists(x+2, y-2)) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y-1);
            if (not bAllowed) and (_misc_IsCollisionExists(x-2, y+2)) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y+1);
            if (not bAllowed) and (_misc_IsCollisionExists(x+2, y+2)) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y+1);
            
            if (not bAllowed) and ((_misc_IsCollisionExists(x-2, y-1)) and (not _misc_IsCollisionExists(x-1, y-2))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y-1) or _misc_IsPlayerWallExists(plInd, x-1, y);
            if (not bAllowed) and ((_misc_IsCollisionExists(x-1, y-2)) and (not _misc_IsCollisionExists(x-2, y-1))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y-1) or _misc_IsPlayerWallExists(plInd, x, y-1);
            if (not bAllowed) and ((_misc_IsCollisionExists(x+2, y-1)) and (not _misc_IsCollisionExists(x+1, y-2))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y-1) or _misc_IsPlayerWallExists(plInd, x+1, y);
            if ((not bAllowed) and (_misc_IsCollisionExists(x+1, y-2)) and (not _misc_IsCollisionExists(x+2, y-1))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y-1) or _misc_IsPlayerWallExists(plInd, x, y-1);
            if ((not bAllowed) and (_misc_IsCollisionExists(x+2, y+1)) and (not _misc_IsCollisionExists(x+1, y+2))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y+1) or _misc_IsPlayerWallExists(plInd, x+1, y);
            if ((not bAllowed) and (_misc_IsCollisionExists(x+1, y+2)) and (not _misc_IsCollisionExists(x+2, y+1))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x+1, y+1) or _misc_IsPlayerWallExists(plInd, x, y+1);
            if ((not bAllowed) and (_misc_IsCollisionExists(x-2, y+1)) and (not _misc_IsCollisionExists(x-1, y+2))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y-1) or _misc_IsPlayerWallExists(plInd, x-1, y);
            if ((not bAllowed) and (_misc_IsCollisionExists(x-1, y+2)) and (not _misc_IsCollisionExists(x-2, y+1))) then
            bAllowed := _misc_IsPlayerWallExists(plInd, x-1, y+1) or _misc_IsPlayerWallExists(plInd, x, y+1);
            
            Result := bAllowed;
         end;
      end;
   end;
   
   if (Result) then
   for [MAIN]tries:=0 to 2 do
   for i := 0 to h-1 do
   for j := 0 to w-1 do
   if GetGameObjectCollisionMaskValueByHandle(buildHnd, i, j) then
   begin
      var testX : Float;
      if w mod 2 = 1 then
      testX := x + (j-(w-1)/2)/2
      else
      testX := x + (j-w/2)/2;
      
      var testY : Float;
      if h mod 2 = 1 then
      testY := y + (i-(h-1)/2)/2
      else
      testY := y + (i-h/2)/2;
      
      if (tries=0) and ((testX < -mapW/2) or (testX > mapW/2) or (testY < -mapH/2) or (testY > mapH/2)) then
      begin
         Result := false;
         break(MAIN);
      end
      else
      begin
         var coltag : Integer;
         if tries=0 then
         coltag := GetMapCollisionTagInRadius(testX, testY, rad, true);
         if (tries=0) and (coltag>0) then
         begin
            if (not bwall) then
            begin
               Result := false;
               break(MAIN);
            end;
         end
         else
         if (tries=0) then
         begin
            var height : Float = RayCastHeight(testX, testY);
            if height<minheight then
            minheight := height
            else
            if height>maxheight then
            maxheight := height;
            if ((maxheight-minheight)>cHeightDelta) then
            begin
               Result := false;
               break(MAIN);
            end;
         end
         else
         if (tries>0) and (bDoScan) then
         begin
            testX := Floor(2*testX)/2+0.25;
            testY := Floor(2*testY)/2+0.25;
            if (tries=1) and (renvminerad>=0) then
            begin
               _misc_MakeListEnvironmentInRadius(testX, testY, renvminerad, False, true, true, true, true, gIntegerList);
               for k:=gIntegerList.GetCount-1 downto 0 do
               begin
                  var goHnd : Integer = gIntegerList.Get(k);
                  var dist : Float = -1;
                  if (_misc_GetBaseObjID(goHnd)=gc_baseid_res) then
                  begin
                     var pres : Pointer = _misc_GetObjectArgData(goHnd, gc_argenv_res);
                     case TRes(pRes).itype of
                        gc_resource_type_wood: begin
                           if (not nearBuild) then
                           dist := envRad;
                        end;
                        gc_resource_type_stone : dist := envRad+0.45;
                        gc_resource_type_food : dist := envRad+0.5;
                        gc_resource_type_gold, gc_resource_type_iron, gc_resource_type_coal : dist := mineRad;
                     end;
                     var distx : Float = Abs(GetGameObjectPositionXByHandle(goHnd)-testX);
                     var disty : Float = Abs(GetGameObjectPositionZByHandle(goHnd)-testY);
                     var angle : Float = _misc_MinFloat(VectorAngle(1, 0, 0, distx, 0, disty), VectorAngle(0, 0, 1, distx, 0, disty));
                     if (dist=mineRad) and (angle>44) then
                     begin
                        distx := distx+1.5;
                        disty := disty+1.5;
                     end;
                     if (dist>=0) and (distx<dist) and (disty<dist) then
                     begin
                        Result := false;
                        break(MAIN);
                     end;
                  end
                  else
                  ErrorLog('_misc_CanPlaceBuilding : wrong baseid');
               end;
            end;
            if (tries=2) and (unitrad>=0) then
            begin
               _misc_MakeListObjectsInRadius(testX, testY, unitrad, False, (1 shl gc_obj_material_body), 0, False, gIntegerList);
               for k:=gIntegerList.GetCount-1 downto 0 do
               begin
                  var goHnd : Integer = gIntegerList.Get(k);
                  if (_misc_GetBaseObjID(goHnd)=gc_baseid_obj) then
                  begin
                     var distx : Float = Abs(GetGameObjectPositionXByHandle(goHnd)-testX);
                     var disty : Float = Abs(GetGameObjectPositionZByHandle(goHnd)-testY);
                     if (distx<unitrad) and (disty<unitrad) then
                     begin
                        Result := false;
                        break(MAIN);
                     end;
                  end
                  else
                  ErrorLog('_misc_CanPlaceBuilding : wrong baseid 2');
               end;
            end;
         end;
      end;
   end;
   _misc_ProfilerEnd('_misc_CanPlaceBuilding');
end;

// _misc_ApplyKeyColorRecursiveChild
//
procedure _misc_ApplyKeyColorRecursiveChild(const goHnd, colorInd : Integer);
begin
   var a : Float;
   if (colorInd>=0) and (colorInd<gc_MaxColorCount) then
   begin
      if (GetLibMaterialTextureFormat(GetGameObjectLibMaterialByHandle(goHnd))=2) then a:=1;
      SetGameObjectUniform4fByHandle(goHnd, 0, gKeyColor[colorInd][0], gKeyColor[colorInd][1], gKeyColor[colorInd][2], a, False);
      var count : Integer = GetGameObjectCountChildByHandle(goHnd);
      var i : Integer;
      for i:=0 to count-1 do
      begin
         var childHnd : Integer = GetGameObjectGOHandleChildByHandle(goHnd, i);
         if (childHnd<>0) then
         _misc_ApplyKeyColorRecursiveChild(childHnd, colorInd);
      end;
   end;
end;

//_misc_CreateBalloon
//
procedure _misc_CreateBalloon(parentHnd : Integer);
begin
   var stx : Float =  GetGameObjectPositionXByHandle(parentHnd);
   var stz : Float =  GetGameObjectPositionZByHandle(parentHnd);
   var minx, miny, minz, maxx, maxy, maxz : Float;
   GetGameObjectCustomBoundingAABBByHandle(parentHnd, minx, miny, minz, maxx, maxy, maxz);
   
   var radius : Float;
   minx := Abs(minx);
   miny := Abs(miny);
   minz := Abs(minz);
   maxx := Abs(maxx);
   maxy := Abs(maxy);
   maxz := Abs(maxz);
   radius := MaxFloat(radius, minx);
   radius := MaxFloat(radius, miny);
   radius := MaxFloat(radius, minz);
   radius := MaxFloat(radius, maxx);
   radius := MaxFloat(radius, maxy);
   radius := MaxFloat(radius, maxz);
   
   var numTries : Integer;
   var plHnd : Integer = GetGameObjectPlayerHandleByHandle(parentHnd);
   var plInd : Integer = GetPlayerIndexByHandle(plHnd);
   var miscPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   var bHnd : Integer = CreatePlayerGameObjectHandleByHandle(miscPlHnd, gc_racename_misc, gc_basename_collision2x2, 0, 0, 0);
   
   var searchPlace : boolean = True;
   
   var curposx : Float;
   var curposz : Float;
   
   while searchPlace do
   begin
      var angle : Float = numTries*20;
      const shiftr = 2.5;
      radius := radius+shiftr;
      curposx := stx+radius*Cos(angle*pi/180)+ _misc_MaxFloat(0, numTries-18)*shiftr;
      curposz := stz+radius*Sin(angle*pi/180)+ _misc_MaxFloat(0, numTries-18)*shiftr;
      var canplace : Boolean = _misc_CanPlaceBuilding(bHnd, plInd, gc_obj_usage_tower, curposx, curposz, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True);
      if (canplace) then
      searchPlace := false
      else
      begin
         if numTries>36 then
         begin
            angle := 270;
            curposx := stx+radius*Cos(angle*pi/180);
            curposz := stz+radius*Sin(angle*pi/180);
            searchPlace := false;
         end
         else
         numTries := numTries+1;
      end;
   end;
   GameObjectDestroyByHandle(bHnd);
   
   var roll : Float;
   var parg: Pointer = StateMachineGetArgDataByInd(GetPlayerStateMachineHandle(plHnd), gc_argplayer_plr);
   TPlayerArgs(parg).fid:=plInd;
   TPlayerArgs(parg).fposx:=curposx;
   TPlayerArgs(parg).fposz:=curposz;
   TPlayerArgs(parg).fdirx:=roll;
   TPlayerArgs(parg).fracename:=gc_racename_misc;
   TPlayerArgs(parg).fbasename:=gc_basename_balloon;
   TPlayerArgs(parg).fplayer:=gc_playerind_misc;
   PlayerExecuteStateByHandle(plHnd, 'WriteNewP');
   var balloonHnd: Integer = TPlayerArgs(parg).fresult;
end;

// _misc_SetShoresCollisionInArea
//
procedure _misc_SetShoresCollisionInArea(minX, minY, maxX, maxY : Float);
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   var i, j, i1, j1 : Integer;
   
   var minI : Integer = ClampInt(Floor(2*minX + w), 0, 2*w-1);
   var minJ : Integer = ClampInt(Floor(2*minY + h), 0, 2*h-1);
   var maxI : Integer = ClampInt(Floor(2*maxX + w), 0, 2*w-1);
   var maxJ : Integer = ClampInt(Floor(2*maxY + h), 0, 2*h-1);
   
   for i := 0 to 2*w-1 do
   for j := 0 to 2*h-1 do
   begin
      var x : Float = (i-w)/2;
      var y : Float = (j-h)/2;
      if GetMapCollisionTag(x, y, false) = gc_collisiontag_water then
      begin
         var border : Boolean;
         for [MAIN]i1 := -1 to 1 do
         for j1 := -1 to 1 do
         begin
            var x1 : Float = x+i1/2;
            var y1 : Float = y+j1/2;
            var tag : Integer = GetMapCollisionTag(x1, y1, false);
            
            if (x1 >= -w/2) and (x1 < w/2) and (y1 >= -h/2) and (y1 < h/2) and ((tag = 0) or (tag = gc_collisiontag_bridge)) then
            begin
               border := true;
               break(MAIN);
            end;
         end;
         
         if border then
         begin
            var rad : Integer = gc_WaterBorderSize;
            for i1 := -rad to rad do
            begin
               var minJ : Integer = MathCeil(Sqrt(rad*rad - i1*i1));
               for j1 := -minJ to minJ do
               if (i1*i1 + j1*j1 < rad*rad) and (x+i1/2 >= -w/2) and (x+i1/2 < w/2) and (y+j1/2 >= -h/2) and (y+j1/2 < h/2) then
               begin
                  var tag : Integer = GetMapCollisionTag(x+i1/2, y+j1/2, false);
                  case tag of
                     0 : MapDrawCollision(x+i1/2, y+j1/2, gc_collisiontag_landborder, 0, false);
                     gc_collisiontag_water : MapDrawCollision(x+i1/2, y+j1/2, gc_collisiontag_waterborder, 0, false);
                  end;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_SetShoresCollision
//
procedure _misc_SetShoresCollision;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   _misc_SetShoresCollisionInArea(0, 0, 2*w-1, 2*h-1);
end;

// _misc_CalcPlayerZonesDist
//
procedure _misc_CalcPlayerZonesDist(plInd : Integer);
begin
   var count : Integer = TopologyGetZonesCount;
   var i, j : Integer;
   for i := 0 to count-1 do
   TopologySetZoneNeedPathByIndex(i, true);
   
   for i := 0 to gc_MaxPlayerCount-1 do
   if (plInd < 0) or (i = plInd) then
   begin
      var cenX : Float = gPlayer[i].aiData.centerX;
      var cenZ : Float = gPlayer[i].aiData.centerZ;
      var cenZone : Integer = TopologyGetZoneIndex(cenX, cenZ);
      
      if cenZone >= 0 then
      begin
         TopologySetZoneNeedPathByIndex(cenZone, false);
         TopologyGetPathToZone(cenZone);
         for j := 0 to count-1 do
         begin
            var pBuffer : Pointer = TopologyGetZoneBufferByIndex(j);
            if pBuffer <> nil then
            begin
               if j = cenZone then
               TTopZone(pBuffer).centerDist[i] := 0
               else
               begin
                  var tpCount : Integer = TopologyGetZoneTrackPointsCountByIndex(j);
                  TTopZone(pBuffer).centerDist[i] := tpCount-1;
               end;
            end;
         end;
         TopologySetZoneNeedPathByIndex(cenZone, true);
      end;
   end;
end;

// _misc_BaseTopologyUpdate
//
procedure _misc_BaseTopologyUpdate(minX, minY, maxX, maxY : Float);
begin
   TopologyUpdate(minX, minY, maxX, maxY);
   _misc_CalcPlayerZonesDist(-1);
end;

// _misc_CreateBridge
//
procedure _misc_CreateBridge(const x1, y1, x2, y2, height : Float);
begin
   const cScale = 1.15;
   //const cBaseHeightOffset = -0.0818;
   const cBaseHeightOffset = 0.4876;
   
   const cBlockWidth = 2.81*cScale;
   const cStartWidth = 1.78*cScale;
   const cEndWidth = 1.78*cScale;
   const cStartOffset = 1.07*cScale;
   const cEndOffset = 1.07*cScale;
   const cCollisionRad = 3;
   const cCollisionStep = 2;
   
   var dist : Float = VectorDistance(x1, 0, y1, x2, 0, y2);
   var blockcount : Integer = floor((dist-cStartWidth/2-cEndWidth/2)/cBlockWidth)+2;
   var dirx : Float = x2-x1;
   var diry : Float;
   var dirz : Float = y2-y1;
   VectorNormalize(dirx, diry, dirz);
   var angle : Float = _misc_GetDirAngleToXVector(dirx, dirz);
   
   var miscplHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   var gohnd : Integer = CreatePlayerGameObjectHandleByHandle(miscplHnd, gc_racename_buildings, 'misbridge4', x1, height, y1);
   GameObjectRollByHandle(gohnd, -45-angle);
   SetGameObjectAutoOffsetByHandle(gohnd, False);
   SetGameObjectPositionByHandle(gohnd, x1, height+cBaseHeightOffset*cScale, y1);
   SetGameObjectScaleByHandle(gohnd, GetGameObjectScaleXByHandle(gohnd)*cScale, GetGameObjectScaleYByHandle(gohnd)*cScale, GetGameObjectScaleZByHandle(gohnd)*cScale);
   
   var x3 : Float = x1+(cStartOffset+cBlockWidth*0.5)*dirx;
   var y3 : Float = y1+(cStartOffset+cBlockWidth*0.5)*dirz;
   var i : Integer;
   for i:=0 to blockcount-1 do
   begin
      gohnd := CreatePlayerGameObjectHandleByHandle(miscplHnd, gc_racename_buildings, 'misbridge'+IntToStr(2+floor(random*2)), x3, height, y3);
      GameObjectRollByHandle(gohnd, -45-angle);
      SetGameObjectAutoOffsetByHandle(gohnd, False);
      SetGameObjectPositionByHandle(gohnd, x3, height+cBaseHeightOffset*cScale, y3);
      SetGameObjectScaleByHandle(gohnd, GetGameObjectScaleXByHandle(gohnd)*cScale, GetGameObjectScaleYByHandle(gohnd)*cScale, GetGameObjectScaleZByHandle(gohnd)*cScale);
      x3 := x3+dirx*cBlockWidth;
      y3 := y3+dirz*cBlockWidth;
   end;
   
   var trgx : Float = x3+dirx*(cEndOffset-cBlockWidth*0.5);
   var trgy : Float = y3+dirz*(cEndOffset-cBlockWidth*0.5);
   gohnd := CreatePlayerGameObjectHandleByHandle(miscplHnd, gc_racename_buildings, 'misbridge1', trgx, height, trgy);
   GameObjectRollByHandle(gohnd, -45-angle);
   SetGameObjectAutoOffsetByHandle(gohnd, False);
   SetGameObjectPositionByHandle(gohnd, trgx, height+cBaseHeightOffset*cScale, trgy);
   SetGameObjectScaleByHandle(gohnd, GetGameObjectScaleXByHandle(gohnd)*cScale, GetGameObjectScaleYByHandle(gohnd)*cScale, GetGameObjectScaleZByHandle(gohnd)*cScale);
   
   // draw collision
   dist := VectorDistance(x1, 0, y1, trgX, 0, trgY);
   var colCount : Integer = MathCeil(dist / cCollisionStep)+1;
   
   var colX : Float = x1 - dirX * (cBlockWidth - cCollisionStep);
   var colZ : Float = y1 - dirZ * (cBlockWidth - cCollisionStep);
   
   for i := 0 to colCount-1 do
   begin
      MapDrawCollision(colX, colZ, gc_collisiontag_bridge, cCollisionRad, true);
      colX := colX + dirX * cCollisionStep;
      colZ := colZ + dirZ * cCollisionStep;
   end;
   
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   
   var minX : Float = MinFloat(x1, trgX) - 2.5 * cStartWidth;
   var minY : Float = MinFloat(y1, trgY) - 2.5 * cStartWidth;
   var maxX : Float = MaxFloat(x1, trgX) + 2.5 * cStartWidth;
   var maxY : Float = MaxFloat(y1, trgY) + 2.5 * cStartWidth;
   
   _misc_SetShoresCollisionInArea(minX, minY, maxX, maxY);
   _misc_BaseTopologyUpdate(minX, minY, maxX, maxY);
end;

// _misc_CreateBridgeExt
//
procedure _misc_CreateBridgeExt(const px, pz, length, angle, height : Float);
begin
   var tx, tz : Float;
   var dirx : Float = 1;
   var diry, dirz : Float;
   VectorRotateY(dirx, diry, dirz, -angle);
   tx := px+length*dirx;
   tz := pz+length*dirz;
   _misc_CreateBridge(px, pz, tx, tz, height);
end;

// _misc_CalcIslandShores
//
procedure _misc_CalcIslandShores;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   if (w <= gc_MaxMapWidth) and (h <= gc_MaxMapHeight) then
   begin
      var i, j, k, i1, j1 : Integer;
      
      var maxX : Integer = 2*w;
      var maxY : Integer = 2*h;
      
      for i := 1 to maxX-2 do
      for j := 1 to maxY-2 do
      if gIslands.grid[i, j].index >= 0 then
      begin
         for [MAIN]i1 := -1 to 1 do
         for j1 := -1 to 1 do
         if gIslands.grid[i+i1, j+j1].index < 0 then
         begin
            gIslands.grid[i, j].shore := true;
            break(MAIN);
         end;
      end;
   end;
end;

// _misc_BuildIslandsGrid
//
procedure _misc_BuildIslandsGrid;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   if (w <= gc_MaxMapWidth) and (h <= gc_MaxMapHeight) then
   begin
      var i, j, k, i1, j1 : Integer;
      
      var maxX : Integer = 2*w;
      var maxY : Integer = 2*h;
      for i := 0 to maxX-1 do
      for j := 0 to maxY-1 do
      begin
         gIslands.grid[i, j].index := -1;
         gIslands.grid[i, j].shore := false;
         gIslands.grid[i, j].checked := false;
      end;
      
      for i := 0 to maxX-1 do
      for j := 0 to maxY-1 do
      begin
         var x : Float = (i-w) / 2 + 0.1;
         var z : Float = (j-h) / 2 + 0.1;
         var tag : Integer = GetMapCollisionTag(x, z, false);
         if (tag < gc_top_TopologyPriority) and (tag <> gc_collisiontag_water) then
         gIslands.grid[i, j].checked := true;
      end;
      
      var island : Integer = 0;
      for i := 0 to maxX-1 do
      for j := 0 to maxY-1 do
      begin
         if gIslands.grid[i, j].checked and (gIslands.grid[i, j].index < 0) then
         begin
            gIntegerList.Clear;
            gIntegerList.Add(i shl 16 + j);
            var minInd : Integer = 0;
            var maxInd : Integer = 0;
            while (minInd <= maxInd) do
            begin
               var oldCount : Integer = gIntegerList.GetCount;
               for k := minInd to maxInd do
               begin
                  var ind : Integer = gIntegerList.Get(k);
                  var indI : Integer = ind shr 16;
                  var indJ : Integer = ind mod (1 shl 16);
                  
                  for i1 := indI-1 to indI+1 do
                  for j1 := indJ-1 to indJ+1 do
                  if (i1 >= 0) and (i1 < maxX) and (j1 >= 0) and (j1 < maxY)
                  and (gIslands.grid[i1, j1].index < 0) and gIslands.grid[i1, j1].checked then
                  begin
                     gIslands.grid[i1, j1].index := island;
                     gIntegerList.Add(i1 shl 16 + j1);
                  end;
               end;
               
               minInd := oldCount;
               maxInd := gIntegerList.GetCount-1;
            end;
            
            island := island+1;
         end;
      end;
      
      gIslands.count := island;
      
      _misc_CalcIslandShores;
   end;
end;

// _misc_IsWaterMap
//
function _misc_IsWaterMap() : Boolean;
begin
   Result := (gMap.settings.gen.terraintype>=2) and (gMap.settings.gen.terraintype<=4);
end;

// _misc_GetArmyInfoPosition
//
procedure _misc_GetArmyInfoPosition(pArmyInfo : Pointer; var x, z : Float);
begin
   if pArmyInfo <> nil then
   begin
      var enGridX : Float = (TArmyInfo(pArmyInfo).minX + TArmyInfo(pArmyInfo).maxX) / 2;
      var enGridY : Float = (TArmyInfo(pArmyInfo).minY + TArmyInfo(pArmyInfo).maxY) / 2;
      _misc_ScanGridIndicesToPos(enGridX, enGridY, x, z);
   end
   else
   begin
      x := 0;
      z := 0;
   end;
end;

// _misc_GetNearestPort
//
function _misc_GetNearestPort(plInd : Integer; posX, posZ : Float) : Integer;
begin
   Result := 0;
   if IsInRangeInt(plInd, 0, gc_MaxPlayerCount-1) then
   begin
      var i : Integer;
      var minDist : Float = gc_MaxFloat;
      for i := gPlayer[plInd].lists.ports.GetCount-1 downto 0 do
      begin
         var hnd : Integer = gPlayer[plInd].lists.ports.Get(i);
         var x : Float = GetGameObjectPositionXByHandle(hnd);
         var z : Float = GetGameObjectPositionZByHandle(hnd);
         var dist : Float = VectorDistance(posX, 0, posZ, x, 0, z);
         if dist < minDist then
         begin
            minDist := dist;
            Result := hnd;
         end;
      end;
   end;
end;

// _misc_TestTransportExitPoint
//
function _misc_TestTransportExitPoint(x, z : Float) : Boolean;
begin
   var mw : Integer = GetMapWidth;
   var mh : Integer = GetMapHeight;
   if IsInRange(x, -mw/2, mw/2) and IsInRange(z, -mh/2, mh/2) then
   begin
      var tag : Integer = GetMapCollisionTag(x, z, true);
      Result := (tag = 0);
   end
   else
   Result := false;
end;

// _misc_FindTransportExitPoint
//
function _misc_FindTransportExitPoint(x, z : Float; rad : Integer; var resX, resZ : Float) : Boolean;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   
   function Check(cx, cz : Float) : Boolean;
   begin
      if (cx >= -w/2) and (cx <= w/2) and (cz >= -h/2) and (cz <= h/2) then
      begin
         Result := _misc_TestTransportExitPoint(cx, cz);
         if Result then
         begin
            resX := cx;
            resZ := cz;
         end;
      end
      else
      Result := false;
   end;
   
   Result := true;
   var i, j, r : Integer;
   var ix : Integer = Floor(2*x+w);
   var iy : Integer = Floor(2*z+h);
   
   var maxRad : Integer = rad;
   if rad < 0 then
   maxRad := Min(Min(ix, 2*w-1-ix), Min(iy, 2*h-1-iy));
   
   for r := 1 to maxRad do
   begin
      for i := -r to r do
      begin
         if Check((ix+i-w)/2+0.1, (iy+r-h)/2+0.1) then exit;
         if Check((ix+i-w)/2+0.1, (iy-r-h)/2+0.1) then exit;
         if Check((ix+r-w)/2+0.1, (iy+i-h)/2+0.1) then exit;
         if Check((ix-r-w)/2+0.1, (iy+i-h)/2+0.1) then exit;
      end;
   end;
   Result := false;
end;

// _misc_FindRallyPointPos
//
function _misc_FindRallyPointPos(x, z : Float; rad : Integer; var resX, resZ : Float) : Boolean;
begin
   var w : Integer = GetMapWidth;
   var h : Integer = GetMapHeight;
   
   function Check(cx, cz : Float) : Boolean;
   begin
      if (cx >= -w/2) and (cx <= w/2) and (cz >= -h/2) and (cz <= h/2) then
      begin
         Result := _misc_TestTransportExitPoint(cx, cz);
         if Result then
         begin
            resX := cx;
            resZ := cz;
         end;
      end
      else
      Result := false;
   end;
   
   Result := true;
   var i, j, r : Integer;
   var ix : Integer = Floor(2*x+w);
   var iy : Integer = Floor(2*z+h);
   
   var maxRad : Integer = rad;
   if rad < 0 then
   maxRad := Min(Min(ix, 2*w-1-ix), Min(iy, 2*h-1-iy));
   
   for r := 1 to maxRad do
   begin
      for i := -r to r do
      begin
         if Check((ix+i-w)/2+0.1, (iy+r-h)/2+0.1) then exit;
         if Check((ix+i-w)/2+0.1, (iy-r-h)/2+0.1) then exit;
         if Check((ix+r-w)/2+0.1, (iy+i-h)/2+0.1) then exit;
         if Check((ix-r-w)/2+0.1, (iy+i-h)/2+0.1) then exit;
      end;
   end;
   Result := false;
end;

// _misc_CheckWallPositions
//
procedure _misc_CheckWallPositions(bFix : Boolean; const logsid : String);
begin
   var pl, i, j : Integer;
   for pl:=0 to gc_MaxPlayerCount-1 do
   begin
      var plHnd : Integer = GetPlayerHandleByIndex(pl);
      for i:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         var goHnd : Integer = GetGameObjectHandleByIndex(i, plHnd);
         var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            var pobjprop : Pointer = gObjProp[TObj(pobj).cid][TObj(pobj).id];
            if (TObjProp(pobjprop).bbuilding) and (TObjProp(pobjprop).bwall) then
            begin
               var x : Float = GetGameObjectPositionXByHandle(goHnd);
               var z : Float = GetGameObjectPositionZByHandle(goHnd);
               var ix : Integer = floor(x*2);
               var iz : Integer = floor(z*2);
               if ((x*2-ix)<>0) or ((z*2-iz)<>0) then
               begin
                  Log('logsid='+logsid+' x='+FloatToStr(x)+' z='+FloatToStr(z));
                  if (bFix) then
                  begin
                     if (ix<0) then ix := ix-1;
                     if (iz<0) then iz := iz-1;
                     SetGameObjectPositionByHandle(goHnd, (ix div 2)+0.5, 0, (iz div 2)+0.5);
                  end;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_SortIntegerLists2D
//
procedure _misc_SortIntegerLists2D(var mainlist, seclist : TIntegerList; grow : Boolean);
begin
   var i, j, k : Integer;
   if (mainlist.GetCount>0) and (mainlist.GetCount=seclist.GetCount) then
   begin
      if (grow) then
      begin
         for i:=0 to mainlist.GetCount-2 do
         begin
            for j:=i+1 to mainlist.GetCount-1 do
            begin
               if (mainlist.Get(i)<mainlist.Get(j)) then
               begin
                  k := mainlist.Get(i);
                  mainlist.Set(mainlist.Get(j), i);
                  mainlist.Set(k, j);
                  k := seclist.Get(i);
                  seclist.Set(seclist.Get(j), i);
                  seclist.Set(k, j);
               end;
            end;
         end;
      end
      else
      begin
         for i:=0 to mainlist.GetCount-2 do
         begin
            for j:=i+1 to mainlist.GetCount-1 do
            begin
               if (mainlist.Get(i)>mainlist.Get(j)) then
               begin
                  k := mainlist.Get(i);
                  mainlist.Set(mainlist.Get(j), i);
                  mainlist.Set(k, j);
                  k := seclist.Get(i);
                  seclist.Set(seclist.Get(j), i);
                  seclist.Set(k, j);
               end;
            end;
         end;
      end;
   end
   else
   ErrorLog('_misc_SortIntegerLists2D : invalid lists');
end;

// _misc_VectorAngle
//
function _misc_VectorAngle(x1, y1, x2, y2 : Float) : Float;
begin
   var angle : Float = VectorAngle(x1, 0, y1, x2, 0, y2);
   var x, y, z : Float;
   VectorCross(x1, 0, y1, x2, 0, y2, x, y, z);
   if y < 0 then
   angle := -angle;
   Result := angle;
end;

// _misc_TopologyWave
//
procedure _misc_TopologyWave(var list : TIntegerList; clear, checked, needPath : Boolean);
begin
   var zoneCount : Integer = TopologyGetZonesCount;
   if (list.GetCount > 0) and (checked or needPath) then
   begin
      var i, j : Integer;
      if clear then
      for i := 0 to zoneCount-1 do
      begin
         //if checked then
         TopologySetZoneCheckedByIndex(i, false);
         //if needPath then
         TopologySetZoneNeedPathByIndex(i, false);
      end;
      
      gIntegerList.Clear;
      var count : Integer = list.GetCount;
      for i := 0 to count-1 do
      begin
         var topZone : Integer = list.Get(i);
         
         if IsInRangeInt(topZone, 0, zoneCount-1) then
         begin
            var add : Boolean = false;
            
            if checked then
            begin
               //if not TopologyGetZoneCheckedByIndex(topZone) then
               //add := true;
               TopologySetZoneCheckedByIndex(topZone, true);
            end;
            
            if needPath then
            begin
               //if not TopologyGetZoneNeedPathByIndex(topZone) then
               //add := true;
               TopologySetZoneNeedPathByIndex(topZone, true);
            end;
            
            //if add then
            gIntegerList.Add(topZone);
         end;
      end;
      
      var minInd : Integer = 0;
      var maxInd : Integer = gIntegerList.GetCount-1;
      while minInd <= maxInd do
      begin
         var oldCount : Integer = gIntegerList.GetCount;
         for i := minInd to maxInd do
         begin
            var zInd : Integer = gIntegerList.Get(i);
            for j := TopologyGetZoneNeighboursCountByIndex(zInd)-1 downto 0 do
            begin
               var nInd : Integer = TopologyGetZoneNeighbourByIndex(zInd, j);
               if (checked and (not TopologyGetZoneCheckedByIndex(nInd))) or
               (needPath and (not TopologyGetZoneNeedPathByIndex(nInd))) then
               begin
                  if checked then
                  TopologySetZoneCheckedByIndex(nInd, true);
                  if needPath then
                  TopologySetZoneNeedPathByIndex(nInd, true);
                  
                  gIntegerList.Add(nInd);
               end;
            end;
         end;
         
         minInd := oldCount;
         maxInd := gIntegerList.GetCount-1;
      end;
   end;
end;

// _misc_GetRegionByPos
//
function _misc_GetRegionByPos(x, y : Float) : Integer;
begin
   var zInd : Integer = TopologyGetZoneIndex(x, y);
   Result := TopologyGetZoneRegionByIndex(zInd);
end;

// _misc_GetMapPlayerIndexByLanID
//
function _misc_GetMapPlayerIndexByLanID(const lanid : Integer) : Integer;
begin
   Result := -1;
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      if (gMap.players[i].bhuman) and (gMap.players[i].lanid=lanid) then
      begin
         Result := i;
         break;
      end;
   end;
end;

// _misc_SetClLanTeamByIndex
//
procedure _misc_SetClLanTeamByIndex(plind, team : Integer);
begin
   if (plind>=0) and (plind<gc_MaxPlayerCount-1) and (team<>gMap.players[plind].team) then
   begin
      if (_net_IsOnline) and ((gInternetShell.bratingroom) or (gInternetShell.bautosearch)) then
      begin
         var newteam : Integer;
         if plind<(gint_quickplay_countofplayers div 2) then
         newteam := 1
         else
         newteam := 2;
         gMap.players[plind].team := newteam;
         LanClSetMyTeam(gMap.players[plind].team);
         Log('_misc_SetClLanTeamByIndex(pl='+IntToStr(plind)+' newteam='+IntToStr(newteam)+')');
      end
      else
      begin
         if (team=0) then
         gMap.players[plind].team := gc_MaxPlayerCount+plind+1
         else
         gMap.players[plind].team := team;
         LanClSetMyTeam(gMap.players[plind].team);
         Log('_misc_SetClLanTeamByIndex(pl='+IntToStr(plind)+' team='+IntToStr(team)+')');
      end;
   end;
end;

// _misc_GetClLanTeamByLanID
//
function _misc_GetClLanTeamByLanID(lanid : Integer) : Integer;
begin
   Result := 0;
   var ind : Integer = _misc_GetMapPlayerIndexByLanID(lanid);
   if (_net_IsOnline) and (gInternetShell.bratingroom) then
   begin
      if ind<(gint_quickplay_countofplayers div 2) then
      Result := 1
      else
      Result := 2;
   end
   else
   if (not gInternetShell.bautosearch) then
   begin
      if (ind>=0) then
      begin
         var team : Integer = gMap.players[ind].team;
         if (team=0) then
         Result := gc_MaxPlayerCount+ind+1
         else
         Result := team;
      end;
   end
   else
   ErrorLog('_misc_GetClLanTeamByLanID : gInternetShell.bautosearch=true');
end;

// _misc_LanForceSyncRes;
//
procedure _misc_LanForceSyncRes();
begin
   if (GetProgressingPauseMode) and (_net_IsServer) then
   begin
      gbool_net_forcesyncres := True;
      var progressPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_progress);
      if (progressPlHnd<>0) then
      PlayerExecuteStateByHandle(progressPlHnd, 'WriteRes');
   end;
end;

// _misc_RemoveFogReveal
//
procedure _misc_RemoveFogReveal(px, pz : Float);
begin
   var miscPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   GetGameObjectsInRadius(px, pz, gc_epsilon, false, false, miscPlHnd, -1, 0, false, false, false, false, false, false);
   var i : Integer;
   for i:=GetGameObjectListCount-1 downto 0 do
   begin
      var goHnd : Integer = GetGameObjectListByIndex(i);
      if (GetGameObjectStringPropertyTag(goHnd)=gc_properties_stringtag_fogreveal) then
      begin
         GameObjectDestroyByHandle(goHnd);
         break;
      end;
   end;
end;

// _misc_GetFogReveal
//
function _misc_GetFogReveal(px, pz : Float) : Integer;
begin
   Result := 0;
   var miscPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   GetGameObjectsInRadius(px, pz, gc_epsilon, false, false, miscPlHnd, -1, 0, false, false, false, false, false, false);
   var i : Integer;
   for i:=GetGameObjectListCount-1 downto 0 do
   begin
      var goHnd : Integer = GetGameObjectListByIndex(i);
      if (GetGameObjectStringPropertyTag(goHnd)=gc_properties_stringtag_fogreveal) then
      begin
         Result := goHnd;
         break;
      end;
   end;
end;

// _misc_GetOrCreateFogReveal
//
function _misc_GetOrCreateFogReveal(px, pz, radius : Float) : Integer;
begin
   Result := _misc_GetFogReveal(px, pz);
   if (Result=0) then
   begin
      var miscPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
      Result := CreatePlayerGameObjectHandleByHandle(miscPlHnd, gc_racename_misc, gc_basename_fogreveal, px, RayCastHeight(px, pz), pz);
      AddFOWObjects(Result);
      SetGameObjectVisibleByHandle(Result, False);
   end;
   var smhnd: Integer=GetGameObjectStateMachineHandle(Result);
   if smhnd<>0 then begin
      var pproj: Pointer=StateMachineGetArgDataByInd(smhnd, gc_argproj_proj);
      if (pproj<>nil) then
      begin
         TProj(pproj).px := px;
         TProj(pproj).pz := pz;
         TProj(pproj).dx := radius;
      end
      else
      ErrorLog('fogreveal proj=nil');
   end
   else
   ErrorLog('fogreveal smhnd=0');
end;

// _misc_GetShotPointsCount
//
function _misc_GetShotPointsCount(goHnd : Integer) : Integer;
begin
   var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if (pobj<>nil) then
   begin
      var cid : Integer = TObj(pobj).cid;
      var id : Integer = TObj(pobj).id;
      Result := gCustomObjPoints[cid][id].shotCount;
   end
   else
   Result := 0;
end;

// _misc_ShotPointsAllowShoot
//
function _misc_ShotPointsAllowShoot(goHnd : Integer; px, pz, tx, tz : Float) : Boolean;
begin
   Result := True;
   var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if (pobj<>nil) then
   begin
      var cid : Integer = TObj(pobj).cid;
      var id : Integer = TObj(pobj).id;
      if (gCustomObjPoints[cid][id].shotCount>0) then
      begin
         Result := False;
         var shotangle : Float = _misc_GetAngleToXVectorInvert(tx, tz, px, pz);
         var ind : Integer;
         var i : Integer;
         for i:=0 to gCustomObjPoints[cid][id].shotCount-1 do
         begin
            if (gCustomObjPoints[cid][id].shotPoints[i].bcustomdir) then
            begin
               var minangle : Float = gCustomObjPoints[cid][id].shotPoints[i].minangle;
               var maxangle : Float = gCustomObjPoints[cid][id].shotPoints[i].maxangle;
               if (minangle>maxangle) then
               begin
                  shotangle := shotangle+360;
                  minangle := minangle+360;
                  maxangle := maxangle+360;
               end;
               if (shotangle>minangle) and (shotangle<maxangle) then
               begin
                  Result := True;
                  break;
               end;
            end
            else
            begin
               Result := True;
               break;
            end;
         end;
      end;
   end;
end;

// _misc_GetCampaignProgressFreePointer
//
function _misc_GetCampaignProgressFreePointer() : Pointer;
begin
   Result := nil;
   var i : Integer;
   for i:=0 to gc_usercampaign_maxcampprogress-1 do
   if (not gProfileUserStruct.campaign.campprogress[i].bexists) then
   begin
      Result := gProfileUserStruct.campaign.campprogress[i];
      break;
   end;
end;

// _misc_GetCampaignProgressPointer
//
function _misc_GetCampaignProgressPointer(campName : String) : Pointer;
begin
   Result := nil;
   var i : Integer;
   for i:=0 to gc_usercampaign_maxcampprogress-1 do
   if (gProfileUserStruct.campaign.campprogress[i].bexists) then
   begin
      if (gProfileUserStruct.campaign.campprogress[i].missname='') and (gProfileUserStruct.campaign.campprogress[i].campname=campName) then
      begin
         Result := gProfileUserStruct.campaign.campprogress[i];
         break;
      end;
   end;
end;

// _misc_GetCampaignProgressPointerExt
//
function _misc_GetCampaignProgressPointerExt(misName : String) : Pointer;
begin
   Result := nil;
   var i : Integer;
   for i:=0 to gc_usercampaign_maxcampprogress-1 do
   if (gProfileUserStruct.campaign.campprogress[i].bexists) then
   begin
      if (gProfileUserStruct.campaign.campprogress[i].missname=misName) then
      begin
         Result := gProfileUserStruct.campaign.campprogress[i];
         break;
      end;
   end;
end;

// _misc_GetCampaignMissionProgressPointer
//
function _misc_GetCampaignMissionProgressPointer(campName, misName : String) : Pointer;
begin
   Result := nil;
   var i : Integer;
   for i:=0 to gc_usercampaign_maxcampprogress-1 do
   if (gProfileUserStruct.campaign.campprogress[i].bexists) then
   begin
      if (gProfileUserStruct.campaign.campprogress[i].missname=misName) and (gProfileUserStruct.campaign.campprogress[i].campname=campName) then
      begin
         Result := gProfileUserStruct.campaign.campprogress[i];
         break;
      end;
   end;
end;

// _misc_GetOrCreateCampaignMissionProgressPointer
//
function _misc_GetOrCreateCampaignMissionProgressPointer(campName, misName : String) : Pointer;
begin
   Result := _misc_GetCampaignMissionProgressPointer(campName, misName);
   if (Result=nil) then
   Result := _misc_GetCampaignProgressFreePointer;
   if (Result<>nil) then
   begin
      TCampaignProgress(Result).bexists := True;
      TCampaignProgress(Result).campname := campName;
      TCampaignProgress(Result).missname := misName;
   end;
end;

// _misc_IsCampaignFinished
//
function _misc_IsCampaignFinished(campName : String) : Boolean;
begin
   Result := False;
   if (campName<>'') then
   begin
      var p : Pointer = _misc_GetCampaignProgressPointer(campName);
      if (p<>nil) then
      Result := (TCampaignProgress(p).finished>0)
   end;
end;

// _misc_IsCampaignFinishedExt
//
function _misc_IsCampaignFinishedExt(misName : String) : Boolean;
begin
   Result := False;
   if (misName<>'') then
   begin
      var p : Pointer = _misc_GetCampaignProgressPointerExt(misName);
      if (p<>nil) then
      Result := (TCampaignProgress(p).finished>0)
   end;
end;

// _misc_IsMissionFinished
//
function _misc_IsMissionFinished(campName, misName : String) : Boolean;
begin
   Result := False;
   if (misName<>'') then
   begin
      var p : Pointer = _misc_GetCampaignMissionProgressPointer(campName, misName);
      if (p<>nil) then
      Result := (TCampaignProgress(p).finished>0);
   end;
end;

// _misc_ClearCampaignMissionProgress
//
procedure _misc_ClearCampaignMissionProgress(campName, misName : String);
begin
   var i : Integer;
   for i:=0 to gc_usercampaign_maxcampprogress-1 do
   if (gProfileUserStruct.campaign.campprogress[i].bexists) then
   begin
      if ((misName='') or (gProfileUserStruct.campaign.campprogress[i].missname=misName)) and ((campName='') or (gProfileUserStruct.campaign.campprogress[i].campname=campName)) then
      gProfileUserStruct.campaign.campprogress[i].Clear;
   end;
end;

// _misc_DoAlarm
//
procedure _misc_DoAlarm(goHnd, trgHnd, event : Integer);
begin
   if (gPlayer[GetPlayerIndexByHandle(GetPlayerHandleInterfaceIO)].lastattacktime=0) then
   begin
      var plIOHnd : Integer = GetPlayerHandleInterfaceIO;
      var plIOInd : Integer = GetPlayerIndexByHandle(plIOHnd);
      var plHnd, trgPlHnd : Integer;
      if (goHnd<>0) then
      plHnd := GetGameObjectPlayerHandleByHandle(goHnd);
      if (trgHnd<>0) then
      trgPlHnd := GetGameObjectPlayerHandleByHandle(trgHnd);
      if (trgPlHnd=plIOHnd) then
      begin
         if (not gSoundManager.IsObjInFrustum(trgHnd)) then
         begin
            gbool_gui_doalarm := True;
            gfloat_gui_alarmx := GetGameObjectPositionXByHandle(trgHnd);
            gfloat_gui_alarmz := GetGameObjectPositionZByHandle(trgHnd);
            if (event=gc_gui_alarmevent_attack) then
            gint_gui_alarmevent := gc_gui_alarmevent_attacked
            else
            if (event=gc_gui_alarmevent_capture) then
            gint_gui_alarmevent := gc_gui_alarmevent_captured
            else
            gint_gui_alarmevent := event;
         end;
      end
      else
      if (plHnd=plIOHnd) then
      begin
         if (not gSoundManager.IsObjInFrustum(goHnd)) then
         begin
            gbool_gui_doalarm := True;
            gfloat_gui_alarmx := GetGameObjectPositionXByHandle(goHnd);
            gfloat_gui_alarmz := GetGameObjectPositionZByHandle(goHnd);
            gint_gui_alarmevent := event;
         end;
      end;
      if (gbool_gui_doalarm) then
      gPlayer[GetPlayerIndexByHandle(plIOHnd)].lastattacktime := (GetCurrentTime+gc_gui_underattackalarminterval); // will work as nextattacktime, to make less calculations
   end;
end;

// _misc_ClampToAvailableTextureQualityOptionsByRAM
//
procedure _misc_ClampToAvailableTextureQualityOptionsByRAM;
begin
   var texturequality : String = GetProjectOptionAsString('DDSDetailLevel');
   var itexturequality : Integer = StrToInt(texturequality);
   var totalphys, availphys, totalvirt, availvirt : Integer;
   GetHardwareRAMInfo(totalphys, availphys, totalvirt, availvirt);
   var ramGB : Integer = round(totalphys/1024) div 1000;
   case ramGB of
      0, 1 : begin
         if (itexturequality<2) then
         begin
            itexturequality := 2;
            texturequality := IntToStr(itexturequality);
            gSettings.video.texturequality := texturequality;
            SetProjectOptionAsString('DDSDetailLevel', texturequality);
         end;
      end;
      2, 3 : begin
         if (itexturequality<1) then
         begin
            itexturequality := 1;
            texturequality := IntToStr(itexturequality);
            gSettings.video.texturequality := texturequality;
            SetProjectOptionAsString('DDSDetailLevel', texturequality);
         end;
      end;
   end;
end;

// _misc_IsEnemyTerritory
//
function _misc_IsEnemyTerritory(plInd : Integer; x, z : Float) : Boolean;
begin
   if gbool_peacemode then
   begin
      var gridX, gridY : Integer;
      if (_misc_PosToScanGridIndices(x, z, gridX, gridY)) then // out of range
      Result := (gScanGrid[gridX, gridY].owner <> plInd)
      else
      Result := false;
   end
   else
   Result := false;
end;

// _misc_ZoomCamera
//
function _misc_ZoomCamera(time, deltaheight : Float; var newx, newy : Float; bSet : Boolean) : Boolean;
begin
   Result := False;
   
   // doesnt support rotation
   var tmp : Float;
   
   const cCameraName = '@movecamera';
   if (not GetCameraExist(cCameraName)) then
   AddCameraInfo(cCameraName);
   
   var dov : Float = GetCameraDepthOfView;
   SetCameraInfoSmoothingTime(time);
   SetCameraInfoSmoothingChange((time>0));
   
   {var pCamera : Integer = _parser_ParserTemporary(True);
   ParserLoadFromFileByHandle(pCamera, '.\data\cameras\camera.cfg');
   var pCameraRestrictInfo : Integer = ParserSelectByHandleByKey(pCamera, 'RestrictInfo');
   var pCameraFreeRotationInfo : Integer = ParserSelectByHandleByKey(pCamera, 'FreeRotationInfo');
   var minHeight : Float = ParserGetFloatValueByKeyByHandle(pCameraRestrictInfo, 'SphereLengthMin');
   var maxHeight : Float = ParserGetFloatValueByKeyByHandle(pCameraRestrictInfo, 'SphereLength');
   var minHeightMinAngle : Float = ParserGetFloatValueByKeyByHandle(pCameraFreeRotationInfo, 'MinHeightMinAngle');
   var minHeightMaxAngle : Float = ParserGetFloatValueByKeyByHandle(pCameraFreeRotationInfo, 'MinHeightMaxAngle');
   var maxHeightMinAngle : Float = ParserGetFloatValueByKeyByHandle(pCameraFreeRotationInfo, 'MaxHeightMinAngle');
   var maxHeightMaxAngle : Float = ParserGetFloatValueByKeyByHandle(pCameraFreeRotationInfo, 'MaxHeightMaxAngle');}
   var lx, rx, fy, by, htm, sh, shm, maxHeight, minHeight : Float;
   GetCameraRestrictInfo(lx, rx, fy, by, htm, sh, shm, maxHeight, minHeight);
   
   var minHeightMinAngle, minHeightMaxAngle, maxHeightMinAngle, maxHeightMaxAngle, angleLerpPower, minDistToTarget, maxDistToTargetm, maxHeightLerpFactor : Float;
   GetCameraFreeRotationInfo( minHeightMinAngle, minHeightMaxAngle, maxHeightMinAngle, maxHeightMaxAngle, angleLerpPower, minDistToTarget, maxDistToTargetm, maxHeightLerpFactor);
   
   var tx, height, tz, cx, cy, cz : Float;
   CameraInfoSaveFromCurrentView(cCameraName);
   CameraInfoReadProperties(cCameraName, tx, height, tz, cx, cy, cz);
   
   var oldHeight : Float = height;
   height := height+deltaheight;
   height := Clamp(height, minHeight, maxHeight);
   
   var angleZ : Float = -Lerp(minHeightMaxAngle, maxHeightMaxAngle, Clamp((height-minHeight)/(maxHeight-minHeight), 0, 1));
   
   var dx : Float;
   var dy : Float;
   var dz : Float = 0.1;
   VectorRotateX(dx, dy, dz, angleZ);
   
   var angleX : Float = VectorAngle(0, 0, 1, dx, 0, dz);
   var dstX : Float = tx;
   var dstZ : Float = tz+ArcTan(-angleZ)*oldHeight;
   VectorRotateY(dstX, tmp, dstZ, angleX);
   
   var offX : Float;
   var offZ : Float = ArcTan(angleZ)*height;
   
   angleX := VectorAngle(0, 0, 1, dx, 0, dz);
   if (dx<0) then angleX := -angleX;
   VectorRotateY(offX, tmp, offZ, angleX);
   
   newx := dstX+offX+dx;
   newy := dstZ+offZ+dz;
   var dst : Float = VectorDistance(cx, cy, cz, newx, height+dy, newy);
   if (dst>0.1) then
   begin
      Result := True;
      
      if (bSet) then
      begin
         CameraInfoSaveWithProperties(cCameraName, dstX+offX, height, dstZ+offZ, dstX+offX+dx, height+dy, dstZ+offZ+dz);
         CameraInfoLoadToCurrentView(cCameraName);
      end;
      SetCameraDepthOfView(dov);
   end;
end;

// _misc_GetNewTextLocale
//
procedure _misc_GetNewTextLocale(const key : String; var text : String);
begin
   text := GetLocaleTableListItemByID('new', key);
   if (text='') then
   begin
      text := GetLocaleTableListItemByID('new_en', key);
      if (text='') then
      text := 'new|'+key;
   end;
end;

// _misc_LanSendReady
//
procedure _misc_LanSendReady();
begin
   if (_net_IsOnline) and (not IsLanReady) then
   begin
      if (LanGetClientsCount>1) then
      LanSendParser(gc_LAN_GAME_READY, _parser_ParserEmpty)
      else
      gMap.gamestage := gc_map_gamestage_started;
   end;
end;

// _misc_LanSendStart
//
procedure _misc_LanSendStart();
begin
   if (gMap.gamestage<gc_map_gamestage_started) and (_net_IsServer) then
   begin
      Log('_misc_LanSendStart');
      LanSendParser(gc_LAN_GAME_START, _parser_ParserEmpty);
      gMap.gamestage := gc_map_gamestage_started;
      var speedfactor : Float;
      case gMap.settings.additional.gamespeed of
         0 : speedfactor := gc_settings_gamespeed_0;
         1 : speedfactor := gc_settings_gamespeed_1;
         2 : speedfactor := gc_settings_gamespeed_2;
         else
         speedfactor := gc_settings_gamespeed_1;
      end;
      FloatRegister0 := speedfactor;
      IntRegister0 := gMap.settings.additional.gamespeed;
      GUIExecuteState('WriteTimeSpeed');
      
      SetProgressingPauseMode(True); // will be switched to False
      GUIExecuteState('WritePause');
   end;
end;

// _misc_GetTSquad
//
function _misc_GetTSquad(plIndex, index : Integer) : Pointer;
begin
   Result := gPlayer[plIndex].squads.Get(index);
   if (gProfile.bDbgLogPointerNil) then
   _init_DebugPointerNil(Result);
end;

// _misc_UpdateSelection
//
procedure _misc_UpdateSelection();
begin
   gbool_gui_update_unitcontrol := True;
   gbool_gui_update_rallypoints := True;
   GUIExecuteState('OnSelection');
end;

// _misc_SetSquadsSelection
//
procedure _misc_SetSquadsSelection(const plInd : Integer; const val : Boolean);
begin
   if plInd >= 0 then
   begin
      var i : Integer;
      for i := gPlayer[plInd].squads.GetCount-1 downto 0 do
      TSquad(gPlayer[plInd].squads.Get(i)).fSelected := val and (TSquad(gPlayer[plInd].squads.Get(i)).GetCount>0);
   end;
end;

// _misc_GetUnitsFromSelection
//
procedure _misc_GetUnitsFromSelection(const plInd : Integer; var list : TIntegerList);
begin
   list.Clear;
   var i, j : Integer;
   for i:=gSelectedObjects.GetCount-1 downto 0 do
   list.Add(gSelectedObjects.Get(i));
   
   for i:=0 to gPlayer[plInd].squads.GetCount-1 do
   begin
      var pSquad : Pointer = _misc_GetTSquad(plInd, i);
      if (pSquad<>nil) and (TSquad(pSquad).fSelected) then
      begin
         for j:=0 to TSquad(pSquad).GetCount-1 do
         list.Add(TSquad(pSquad).Get(j));
      end;
   end;
end;

// _misc_SetUnitsToSelection
//
procedure _misc_SetUnitsToSelection(plInd : Integer; var list : TIntegerList);
begin
   ClearGameManagerPickedObjects;
   var i : Integer;
   _misc_SetSquadsSelection(plInd, false);
   
   GUIExecuteState('OnSelection');
   var count : Integer = list.GetCount;
   if (count>0) then
   begin
      GameManagerBeginSelection;
      GameManagerStartSelection('spmRunTime');
      var i : Integer;
      for i:=count-1 downto 0 do
      begin
         var goHnd : Integer = list.Get(i);
         var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            if TObj(pObj).squad < 0 then
            gSelectedObjects.Add(goHnd)
            else
            begin
               var plHnd : Integer = GetGameObjectPlayerHandleByHandle(goHnd);
               var plInd : Integer = GetPlayerIndexByHandle(plHnd);
               var pSquad : Pointer = _misc_GetTSquad(plInd, TObj(pObj).squad);
               
               if (pSquad<>nil) and (TSquad(pSquad).GetCount>0) then
               TSquad(pSquad).fSelected := true;
            end;
            
            SetGameObjectPickedByHandle(goHnd, True);
            if (i=count-1) then
            GameManagerCancelSelection;
            gGuiUpdateHighlights.Add(goHnd);
         end;
      end;
   end;
   _misc_UpdateSelection;
end;

// _misc_AddUnitsToSelection
//
procedure _misc_AddUnitsToSelection(var list : TIntegerList);
begin
   // вспомогательные функции, добавляет в группу селекшона юнитов из списка
   var count : Integer = list.GetCount;
   if (count>0) then
   begin
      GameManagerBeginSelection;
      GameManagerStartSelection('spmRunTime');
      var i : Integer;
      for i:=count-1 downto 0 do
      begin
         var goHnd : Integer = list.Get(i);
         SetGameObjectPickedByHandle(goHnd, True);
         if (i=count-1) then
         GameManagerCancelSelection;
         gGuiUpdateHighlights.Add(goHnd);
      end;
      _misc_UpdateSelection;
   end;
end;

// _misc_RemoveUnitsFromSelection
//
procedure _misc_RemoveUnitsFromSelection(var list : TIntegerList);
begin
   // вспомогательные функции, удаляет из группы селекшона юнитов из списка
   var count : Integer = list.GetCount;
   if (count>0) then
   begin
      GameManagerBeginSelection;
      GameManagerStartSelection('spmRunTime');
      var i : Integer;
      for i:=count-1 downto 0 do
      begin
         var goHnd : Integer = list.Get(i);
         if (GetGameObjectPickedByHandle(goHnd)) then
         begin
            SetGameObjectPickedByHandle(goHnd, False);
            gSelectedObjects.Remove(goHnd);
         end;
         gGuiUpdateHighlights.Add(goHnd);
      end;
      GameManagerCancelSelection;
      _misc_UpdateSelection;
   end;
end;

// _misc_SelectAllUnitsOfType
//
procedure _misc_SelectAllUnitsOfType(cid, id : Integer; bSelect, bOnAllMap, bShift, bSquadInclude : Boolean);
begin
   gTmpIntList.Clear;
   var myPlHnd : Integer = GetPlayerHandleInterfaceIO;
   var myPlInd : Integer = GetPlayerIndexByHandle(myPlHnd);
   if (bShift) then
   _misc_GetUnitsFromSelection(myPlInd, gTmpIntList);
   
   GameManagerBeginSelection;
   GameManagerStartSelection('spmRunTime');
   var bNeedCancelDoneOnce, bCancelDoneOnce : Boolean;
   if (GameManagerIsSelectionMode) then
   begin
      var i : Integer;
      
      // initialize game selections
      if not bShift then
      begin
         gSelectedObjects.Clear;
         _misc_SetSquadsSelection(myPlInd, false);
      end
      else
      begin
         for i := gSelectedObjects.GetCount-1 downto 0 do
         begin
            var goHnd : Integer = gSelectedObjects.Get(i);
            var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
            if (pobj<>nil) and (TObj(pobj).id=id) and (TObj(pobj).cid=cid) then
            gSelectedObjects.Delete(i);
         end;
      end;
      
      for i:=GetPlayerGameObjectsCountByHandle(myPlHnd)-1 downto 0 do
      begin
         var goHnd : Integer = GetGameObjectHandleByIndex(i, myPlHnd);
         if (GetGameObjectPlayableObjectByHandle(goHnd)) and (GetGameObjectVisibleByHandle(goHnd)) then
         begin
            var bAdd, bRemove : Boolean;
            
            if (bOnAllMap) or (GetGameObjectIsVolumeClippedByHandle(goHnd)) then
            begin
               var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
               if (pobj<>nil) and (TObj(pobj).id=id) and (TObj(pobj).cid=cid) then
               begin
                  if (bSelect) then
                  begin
                     var formInd : Integer = TObj(pObj).squad;
                     
                     if bSquadInclude or (formInd < 0) then
                     bAdd := true
                     else
                     bRemove := true;
                     
                     if formInd < 0 then
                     gSelectedObjects.Add(goHnd)
                     else
                     if bSquadInclude then
                     begin
                        var pSquad : Pointer = _misc_GetTSquad(myPlInd, formInd);
                        TSquad(pSquad).fSelected := (TSquad(pSquad).GetCount>0);
                     end;
                  end
                  else
                  bRemove := True;
               end
               else
               if (not bShift) then
               bRemove := True;
            end
            else
            if (not bShift) then
            bRemove := True;
            
            if (bAdd) then
            begin
               SetGameObjectPickedByHandle(goHnd, True);
               bNeedCancelDoneOnce := True;
            end
            else
            if (bRemove) then
            SetGameObjectPickedByHandle(goHnd, False);
            
            gGuiUpdateHighlights.Add(goHnd);
            if (not bCancelDoneOnce) and (bNeedCancelDoneOnce) then
            begin
               GameManagerCancelSelection;
               bCancelDoneOnce := True;
            end;
         end;
         if (not bCancelDoneOnce) and (bNeedCancelDoneOnce) then
         begin
            GameManagerCancelSelection;
            bCancelDoneOnce := True;
         end;
      end;
      if (not bCancelDoneOnce) then
      GameManagerCancelSelection;
      _misc_UpdateSelection;
   end;
   if (bShift) then
   _misc_AddUnitsToSelection(gTmpIntList);
end;

// _misc_ScanGridCellDataCheckNeeded
//
function _misc_ScanGridCellDataCheckNeeded(var grid : TGridList; plmask, mask, scanmode : Integer) : Boolean;
begin
   if scanmode=1 then
   Result := (grid.fPlMask<>0)
   else
   begin
      if (grid.fPlMask and plmask=0) or (grid.fMask and mask=0) then
      Result := False
      else
      if ((GetGameTime-grid.fSELastCheckTime)>gfloat_searchenemygridcachetime)
      or (grid.fSELastCheckMask<>mask) or (grid.fSELastCheckPlMask<>plmask) then
      begin
         grid.fSELastCheckMask := mask;
         grid.fSELastCheckPlMask := plmask;
         Result := True;
      end
      else
      Result := grid.fSELastCheckResult;
   end;
end;

// _misc_ScanGridCellDataUpdateResult
//
procedure _misc_ScanGridCellDataUpdateResult(var grid : TGridList; res : Boolean);
begin
   grid.fSELastCheckTime := GetGameTime;
   grid.fSELastCheckResult := res;
end;

// _misc_GetRealTimeOld
//
function _misc_GetRealTimeOld() : Float;
begin
   Result := GetRealTime*24*3600;
end;

// _misc_GetRealTime
//
function _misc_GetRealTime() : Float;
begin
   var newrealtime : Float = GetRealTime*24*3600;

   if (newrealtime<gPrevRealTime) then
   gRealTimeProcessed := gRealTimeProcessed+24*3600;

   Result := gRealTimeProcessed+newrealtime;

   gPrevRealTime := newrealtime;
end;

// _misc_PlaySound
//
function _misc_PlaySound(const libName, sndName : String) : Integer;
begin
   //log('playsound='+sndName);
   Result := 0;
   var sndtag : Integer = gint_gui_soundtag+1;
   if (Abs(GetCurrentTime-gfloat_gui_lastguisoundtime)>0.3) or (gstring_gui_lastguisoundname<>sndName) then
   begin
      gint_gui_soundtag := sndtag;
      gstring_gui_lastguisoundname := sndName;
      gfloat_gui_lastguisoundtime := GetCurrentTime;
      var sndHnd : Integer = SndGetOrCreateSound(sndtag, libName, GetSoundManagerListenerHandle);
      SetSndSoundPlaying(False, sndHnd);
      SetSndSoundSourceName(sndName, sndHnd);
      SetSndSoundPlaying(True, sndHnd);
      if (False) then
      log(libName+' '+sndName);
      Result := sndHnd;
   end;
end;

// _misc_IncreaseResGridWorkerCount
//
procedure _misc_IncreaseResGridWorkerCount(const goHnd, trgHnd : Integer);
begin
   var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if (pobj<>nil) and (trgHnd<>0) then
   begin
      var pordlist : Pointer = _misc_GetObjectArgData(trgHnd, gc_argunit_ordlist);
      if (pordlist<>nil) then
      begin
         var bres : Boolean = (_misc_GetBaseObjID(trgHnd)=gc_baseid_res);
         var bexist : Boolean;
         if (bres) and (TIntegerList(pordlist).IndexOf(goHnd)>=0) then
         bexist := True;
         TIntegerList(pordlist).Add(goHnd);
         // add workercounter to resgrid
         if (bres) and (not bexist) then
         begin
            var pres : Pointer = _misc_GetObjectArgData(trgHnd, gc_argenv_res);
            if (pres<>nil) and (TRes(pres).resgrid>-1) then
            begin
               var gridX : Integer = TRes(pres).resgrid mod gc_resgrid_countx;
               var gridY : Integer = TRes(pres).resgrid div gc_resgrid_countx;
               if (IsInRangeInt(gridX, 0, gc_resgrid_countx)) and (IsInRangeInt(gridY, 0, gc_resgrid_county)) then
               gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype] := gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype]+1;
            end;
         end;
      end
      else
      ErrorLog('_misc_IncreaseResGridWorkerCount : pordlist=nil');
   end;
end;

// _misc_DecreaseResGridWorkerCount
//
procedure _misc_DecreaseResGridWorkerCount(const goHnd, ind : Integer);
begin
   var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
   if (pobj<>nil) and (TObj(pobj).orders[ind].info.trg<>0) then
   begin
      var pordlist : Pointer = _misc_GetObjectArgData(TObj(pobj).orders[ind].info.trg, gc_argunit_ordlist);
      if (pordlist<>nil) then
      begin
         TIntegerList(pordlist).Remove(goHnd);
         var bres : Boolean = (_misc_GetBaseObjID(TObj(pobj).orders[ind].info.trg)=gc_baseid_res);
         var bexist : Boolean = True;
         if (bres) and (TIntegerList(pordlist).IndexOf(goHnd)=-1) then
         bexist := False;
         // remove workercounter from resgrid
         if (bres) and (not bexist) then
         begin
            var pres : Pointer = _misc_GetObjectArgData(TObj(pobj).orders[ind].info.trg, gc_argenv_res);
            if (pres<>nil) and (TRes(pres).resgrid>-1) then
            begin
               var gridX : Integer = TRes(pres).resgrid mod gc_resgrid_countx;
               var gridY : Integer = TRes(pres).resgrid div gc_resgrid_countx;
               if (IsInRangeInt(gridX, 0, gc_resgrid_countx)) and (IsInRangeInt(gridY, 0, gc_resgrid_county)) then
               begin
                  gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype] := gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype]-1;
                  if (gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype]<0) then
                  Log('Attention : _unit_SetOrderTrg : gResGrid['+IntToStr(gridX)+','+IntToStr(gridY)+'].fWorkerCount[TRes(pres).itype]<0 = '+IntToStr(gResGrid[gridX,gridY].fWorkerCount[TObj(pobj).pl][TRes(pres).itype]));
               end;
            end;
         end;
      end;
   end;
end;

// _misc_ScanGridAddByPlayer
//
procedure _misc_ScanGridAddByPlayer_OLD(const gridx, gridy, gohnd, mat, pl: Integer);
begin
   gScanGridUnits[pl, gridx, gridy].Add(gohnd);
   var pscangrid : Pointer = gScanGrid[gridx, gridy];
   TGridList(pscangrid).Add(gohnd, mat, pl);
   if (TGridList(pscangrid).fMatCount[mat]=0) then
   TGridList(pscangrid).fMask := TGridList(pscangrid).fMask or (1 shl mat);
   if (TGridList(pscangrid).fPlCount[pl]=0) then
   TGridList(pscangrid).fPlMask := TGridList(pscangrid).fPlMask or (1 shl pl);
   TGridList(pscangrid).fMatCount[mat]:=TGridList(pscangrid).fMatCount[mat]+1;
   TGridList(pscangrid).fPlCount[pl]:=TGridList(pscangrid).fPlCount[pl]+1;
end;

// _misc_ScanGridRemoveByPlayer
//
procedure _misc_ScanGridRemoveByPlayer_OLD(const gridx, gridy, gohnd, mat, pl: Integer);
begin
   gScanGridUnits[pl, gridx, gridy].Remove(gohnd);
   var pscangrid : Pointer = gScanGrid[gridx, gridy];
   TGridList(pscangrid).Remove(gohnd, mat, pl);
   TGridList(pscangrid).fMatCount[mat]:=TGridList(pscangrid).fMatCount[mat]-1;
   TGridList(pscangrid).fPlCount[pl]:=TGridList(pscangrid).fPlCount[pl]-1;
   if (TGridList(pscangrid).fMatCount[mat]<=0) then
   TGridList(pscangrid).fMask := TGridList(pscangrid).fMask xor (1 shl mat);
   if (TGridList(pscangrid).fPlCount[pl]<=0) then
   TGridList(pscangrid).fPlMask := TGridList(pscangrid).fPlMask xor (1 shl pl);
end;

// _misc_GetRankByScoreELO
//
function _misc_GetRankByScoreELO(score : Integer) : Integer;
begin
   Result := gQuickPlay.GetRankByScoreELO(score);
end;

// _misc_GetRankByScore
//
function _misc_GetRankByScore(score : Integer) : Integer;
begin
   Result := gQuickPlay.GetRankByScore(score);
end;

// _misc_IntToRoman
//
procedure _misc_IntToRoman(value : Integer; var res : String);
begin
   if value=0 then // support nulla or nihil;
   begin
      res := 'N';
      exit;
   end;
   var a : array [0..12] of Integer;
   var r : array [0..12] of String;
   a[0]:=1000; r[0]:='M';
   a[1]:=900; r[1]:='CM';
   a[2]:=500; r[2]:='D';
   a[3]:=400; r[3]:='CD';
   a[4]:=100; r[4]:='C';
   a[5]:=90; r[5]:='XC';
   a[6]:=50; r[6]:='L';
   a[7]:=40; r[7]:='XL';
   a[8]:=10; r[8]:='X';
   a[9]:=9;  r[9]:='IX';
   a[10]:=5; r[10]:='V';
   a[11]:=4; r[11]:='IV';
   a[12]:=1; r[12]:='I';
   var i : Integer;
   var num : Integer = value;
   if num<0 then
   num := -num;
   while (num>0) do
   begin
      while (num>=a[i]) do
      begin
         res := res + r[i];
         num := num - a[i];
      end;
      i := i+1;
      if i>12 then // should not be here ever
      break;
   end;
   if value<0 then
   res := '-'+res;
end;

// _misc_TimeToStr
//
procedure _misc_TimeToStr(ftime : Float; var str : String; bshowzeromin : Boolean);
begin
   var time : Integer = floor(ftime);
   var hour : Integer = time div 3600;
   var min : Integer = (time mod 3600) div 60;
   var sec : Integer = (time mod 3600) mod 60;
   var stime : String;
   if (hour>0) then
   stime := IntToStr(hour)+':';
   if (min>0) or (hour>0) then
   begin
      if (hour>0) and (min<10) then stime := stime+'0';
      stime := stime+IntToStr(min)+':';
   end;
   if ((hour>0) or (min>0)) and (sec<10) then stime := stime+'0';
   stime := stime+IntToStr(sec);
   if (bshowzeromin) and (hour=0) and (min=0) then
   stime := '0:'+stime;
   str := stime;
end;

// _misc_TwoIntToOne
//
function _misc_TwoIntToOne(int1, int2 : Integer) : Integer;
begin
   Result := int1*32768+int2;
end;

// _misc_OneIntToTwo
//
procedure _misc_OneIntToTwo(val : Integer; var int1, int2 : Integer);
begin
   int1 := val div 32768;
   int2 := val mod 32768;
end;

// _misc_CompareVariables
//
function _misc_CompareVariables(const pvar1, pvar2 : Pointer; size : Integer) : Boolean;
begin
   Result := True;
   var i : Integer;
   for i:=0 to size-1 do
   begin
      if (Byte(pvar1+i)<>Byte(pvar2+i)) then
      begin
         Result := False;
         break;
      end;
   end;
end;

// _misc_IsWindows
//
function _misc_IsWindows() : Boolean;
begin
   Result := StrExists(GetApplicationFileName, '.exe');
end;

// _misc_IsLinux
//
function _misc_IsLinux() : Boolean;
begin
   Result := not StrExists(GetApplicationFileName, '.exe');
end;

// _misc_IsWindowsBySlash
//
function _misc_IsWindowsBySlash() : Boolean;
begin
   Result := (StrExists(GetApplicationFileName, '\')) or (StrExists(GetAppSaveDirectoryPath, '\'));
end;

// _misc_IsLinuxBySlash
//
function _misc_IsLinuxBySlash() : Boolean;
begin
   Result := (StrExists(GetApplicationFileName, '/')) or (StrExists(GetAppSaveDirectoryPath, '/'));
end;

// _misc_GetInfoFromCountry
//
procedure _misc_GetInfoFromCountry(var country : String; var ps, pw, pg : Integer);
begin
   var i : Integer;
   for i:=0 to GetDelimiterStringCount(country, gc_gui_delimiterchar, gc_gui_quotechar)-1 do
   begin
      var s : String = GetDelimiterStringByIndex(country, gc_gui_delimiterchar, gc_gui_quotechar, i);
      var pref : String = SubStr(s, 1, 3);
      var suff : String = SubStr(s, 4, StrLength(s)-3);
      var val : Integer;
      case pref of
         'ps=' : ps := StrToInt(suff);
         'pw=' : pw := StrToInt(suff);
         'pg=' : pg := StrToInt(suff);
      end;
   end;
end;
