//
// miscext2.script
//

// _map_RestoreSettings
//
procedure _map_RestoreSettings;
begin
   var border, needed: Boolean;
   var filename, zonename, presetname: String;
   var w, h, c, k0, k1, setind, seasind, watind: Integer;
   var mapinffile, trigfile, sndvolfile, ambsndfile, inimachfile, inimachstate, guifile, guistate, lightname, casttype, poststate: String;
   GetMapNeeded(filename, zonename, presetname, w, h, c, k0, k1, setind, seasind, watind, mapinffile, trigfile, sndvolfile, ambsndfile, inimachfile, inimachstate, guifile, guistate, lightname, casttype, poststate, border, needed);
   if(filename='') and (zonename='') and (presetname='') then begin
      filename:='game_v'+IntToStr(gSerialVersion)+'k'+IntToStr(gMap.settings.gen.randkey1)+'.map';
      zonename:='Default';
      presetname:='Default';
      c:=1;
      case gMap.settings.gen.mapsize of
         0: w:=320;
         1: w:=480;
         2: w:=640;
         3: w:=256;
         else
         w:=320;
      end;
      k0:=gMap.settings.gen.randkey0;
      k1:=gMap.settings.gen.randkey1;
      setind:=0;
      seasind:=0;
      watind:=0;
      mapinffile:='';
      trigfile:='';
      sndvolfile:='';
      ambsndfile:='';
      inimachfile:='';
      inimachstate:='';
      guifile:='.\data\gui\menu.cfg';
      guistate:='';
      lightname:='light0';
      casttype:='';
      poststate:='InitMapGen';
      border:=false;
      needed:=false;
      SetMapNeeded(filename, zonename, presetname, w, w, c, k0, k1, setind, seasind, watind, mapinffile, trigfile, sndvolfile, ambsndfile, inimachfile, inimachstate, guifile, guistate, lightname, casttype, poststate, border, needed);
   end;
end;

// _misc_PrepareCheckAccessControlRequirementsText
//
function _misc_PrepareCheckAccessControlRequirementsText(var text : String; const plHnd, cid : Integer; var sid : String) : Integer;
begin
   text := '';
   // Проверка, country (национальных) запретов на производство юнита или совершение апгрейда
   Result := gc_result_checkaccesscontrolreq_ok;
   var plInd : Integer = GetPlayerIndexByHandle(plHnd);

   // Определяаем, является ли запрашиваемый объект юнитом или апгрейдом
   var bMember : Boolean = _country_IsCountryMember(cid, sid);
   var bUpgrade : Boolean;
   if (bMember) then
   bUpgrade := False
   else
   bUpgrade := _country_IsCountryUpgrade(cid, sid);
   if (not bMember) and (not bUpgrade) then
   ErrorLog('_misc_PrepareCheckAccessControlRequirementsText : Unknown sid = '+sid);

   // Разрешен ли данный юнит для производства. Не выполняется ли в данный момент этот апгрейд (одновременное выполнение одного и того же апгрейда запрещено).
   var id : Integer;
   var bEnabled : Boolean;
   if (bMember) then
   begin
      bEnabled := (_country_GetEnabledIndexBySID(cid, sid)>-1) and (gPlayer[plInd].objbase[cid][id].bproduceenabled);
      id := _unit_ConvertObjSIDToID(cid, sid);
   end
   else
   if (bUpgrade) then
   begin
      var upgind : Integer = _country_GetUpgradeIndexByUpgradeID(cid, sid, False);
      bEnabled := (gPlayer[plInd].upgstate[cid][upgind].enabled) and (not (gPlayer[plInd].upgstate[cid][upgind].done) and (not gPlayer[plInd].upgstate[cid][upgind].bscenariodisabled));
   end;

   var cLockCountBuilding : String = gc_font_color_red+GetLocaleTableListItemByID('gui', 'unitpanel.hint.lockcountbuilding')+gc_font_color_white;
   var cLockCountUnit : String = gc_font_color_red+GetLocaleTableListItemByID('gui', 'unitpanel.hint.lockcountunit')+gc_font_color_white;
   var cArtLimit : String = gc_font_color_red+GetLocaleTableListItemByID('gui', 'unitpanel.hint.artlimit')+gc_font_color_white;
   var cPrerequisitBuildings : String = gc_font_color_red+GetLocaleTableListItemByID('gui', 'unitpanel.hint.prerequisitbuildings')+gc_font_color_white;
   var cPrerequisitUpgrades : String = gc_font_color_red+GetLocaleTableListItemByID('gui', 'unitpanel.hint.prerequisitupgrades')+gc_font_color_white;
   gTmpIntListMembers.Clear;
   gTmpIntListUpgrades.Clear;

   if (bEnabled) then
   begin
      if (bMember) then
      begin
         // Не заблокировано ли производство механизмом LockCount (ограничение на количество юнитов/построек одного типа у игрока).
         var ind : Integer = _country_GetUnitLockIndexBySID(cid, sid, False);
         if (ind>=0) then
         begin
            var locksid : String = gCountry[cid].unitlock[ind].lockid;
            var lockid : Integer = _unit_ConvertObjSIDToID(cid, locksid);
            var count : Integer = gPlayer[plInd].counter.all[cid][lockid];
            var lockcount : Integer = gCountry[cid].unitlock[ind].count;
            if (count>=lockcount) then
            begin
               Result := gc_result_checkaccesscontrolreq_lockcount;
               if (gObjProp[cid][id].bbuilding) then
               text := cLockCountBuilding
               else
               text := cLockCountUnit;
            end;
         end;
         if (Result=gc_result_checkaccesscontrolreq_ok) then
         begin
            if (gObjProp[cid][id].bArtillery) and (gPlayer[plInd].artcount[gObjProp[cid][id].artind]>=gPlayer[plInd].artlimit[gObjProp[cid][id].artind]) then
            begin
               Result := gc_result_checkaccesscontrolreq_artlimit;
               text := cArtLimit;
            end;
         end;
      end;
      if (Result=gc_result_checkaccesscontrolreq_ok) then
      begin
         // Сверка списка необходимых построек/апгрейдов для данной постройки или апгрейда, с имеющимися у игрока постройками и выполненными апгрейдами
         var ind : Integer = _country_GetAccessControlIndexBySID(cid, sid, False);
         if (ind>=0) then
         begin
            var i : Integer;
            for i:=0 to gc_country_maxaccesscontrolreqs-1 do
            begin
               var reqsid : String = gCountry[cid].accesscontrol[ind].req[i];
               if (reqsid<>'') then
               begin
                  if (_country_IsCountryMember(cid, reqsid)) then
                  begin
                     var reqid : Integer = _unit_ConvertObjSIDToID(cid, reqsid);
                     if (reqid>0) and (reqsid<>'') then
                     begin
                        if (gPlayer[plInd].counter.built[cid][reqid]<=0) then
                        begin
                           Result := gc_result_checkaccesscontrolreq_noprerequisit;
                           gTmpIntListMembers.Add(reqid);
                        end;
                     end
                     else
                     break;
                  end
                  else
                  begin
                     var upgind : Integer = _country_GetUpgradeIndexByUpgradeID(cid, reqsid, False);
                     if (upgind>0) then
                     begin
                        var requpgind : Integer;
                        if (gPlayer[plInd].upgstate[cid][upgind].sid=reqsid) then
                        requpgind := upgind
                        else
                        begin
                           var j : Integer;
                           for j:=0 to gc_MaxCountryCount-1 do
                           begin
                              if (gPlayer[plInd].upgstate[cid][j].sid=reqsid) then
                              begin
                                 requpgind := j;
                                 break;
                              end;
                           end;
                        end;
                        if (requpgind>0) then
                        begin
                           if (not gPlayer[plInd].upgstate[cid][requpgind].done) then
                           begin
                              Result := gc_result_checkaccesscontrolreq_noprerequisit;
                              gTmpIntListUpgrades.Add(requpgind);
                           end;
                        end
                        else
                        ErrorLog('_misc_PrepareCheckAccessControlRequirementsText : Unknown requpgsid = '+reqsid)
                     end
                     else
                     ErrorLog('_misc_PrepareCheckAccessControlRequirementsText : Unknown reqsid = '+reqsid);
                  end;
               end;
            end;
         end;
      end;
   end
   else
   Result := gc_result_checkaccesscontrolreq_notenabled;

   if (Result=gc_result_checkaccesscontrolreq_noprerequisit) then
   begin
      var i : Integer;
      if (gTmpIntListMembers.GetCount>0) then
      begin
         text := cPrerequisitBuildings;
         for i:=0 to gTmpIntListMembers.GetCount-1 do
         begin
            text := text+gc_gui_breakline;
            var reqid : Integer = gTmpIntListMembers.Get(i);
            var reqsid, stmp : String;
            _unit_ConvertObjIDToSID(cid, reqid, reqsid);
            _misc_GetUnitNameBySID(reqsid, stmp);
            text := text+stmp;
         end;
      end;
      if (gTmpIntListUpgrades.GetCount>0) then
      begin
         text := cPrerequisitUpgrades;
         for i:=0 to gTmpIntListUpgrades.GetCount-1 do
         begin
            text := text+gc_gui_breakline;
            var requpgind : Integer = gTmpIntListUpgrades.Get(i);
            var requpgsid : String = gCountry[cid].upgrade[requpgind].id;
            var stmp : String;
            _country_GetUpgradeNameBySID(cid, requpgsid, stmp);
            text := text+stmp;
         end;
      end;
   end;
end;

//_misc_DoWallDamage
//
procedure _misc_DoWallDamage(const goHnd, method : Integer);
begin
   var goX : Float = GetGameObjectPositionXByHandle(goHnd);
   var goZ : Float = GetGameObjectPositionZByHandle(goHnd);
   if (method=gc_wall_damage_cdel) then
   begin
      EffectHighlightRemove(goHnd, ''); // execute before EffectClear
      EffectClear(goHnd); // execute after EffectHighlightRemove
      GameObjectExecuteStateByHandle(goHnd, 'OnDeath');
      _unit_SetTagStates(goHnd, gc_statetag_essential_death or gc_statetag_visual_hide);
   end
   else
   begin
      const rad : Float = 1.5;
      _misc_MakeListObjectsInRadius(goX, goZ, rad, true, (1 shl gc_obj_material_building) or (1 shl gc_obj_material_woodwall), 0, True, gIntegerList);
      var i : Integer;
      for i:=0 to gIntegerList.GetCount-1 do
      begin
         var objHnd : Integer = gIntegerList.Get(i);
         if (objHnd<>goHnd) then
         begin
            var usage : Integer = _unit_GetUsage(objHnd);
            if (usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall) then
            case method of
               gc_wall_damage_kill : begin
                  GameObjectExecuteStateByHandle(objHnd, 'OnDeath');
                  _unit_SetTagStates(objHnd, gc_statetag_essential_death);
               end;
               gc_wall_damage_del : _unit_SetTagStates(objHnd, gc_statetag_essential_death);
            end;
         end;
      end;
   end;
end;

// _misc_ShowAlarm
//
function _misc_ShowAlarm(px, pz : Float) : Integer;
begin
   var prim : Integer = CreateGUIMiniMapPrimitive('lens.0');
   Result := prim;
   if (prim>-1) then
   begin
      gPrimitives.Add(prim);
      SetGUIMiniMapPrimitiveTag(prim, floor(GetCurrentTime));
      SetGUIMiniMapPrimitiveBlink(prim, 0.2, floor(10/0.2)*10);
      SetGUIMiniMapPrimitivePosition(prim, round(px), round(pz));
      SetGUIMiniMapPrimitiveVisible(prim, True);
   end;
end;

// _misc_DoDamage
//
procedure _misc_DoDamage(const goHnd, trgHnd, indamage, weapind, weapkind : Integer);
begin
   if (trgHnd<>0) then
   begin
      var pobj2 : Pointer = _unit_GetTObj(trgHnd);
      var bSkipOnDeath : Boolean;

      if (pobj2<>nil) and (not TObj(pobj2).bdead) then
      begin
         if (gScenario.bactive) and ((TObj(pobj2).hp>=gc_gameplay_infinitehp) or (not GetGameObjectPlayableObjectByHandle(trgHnd))) then
         exit;

         var pobj : Pointer = nil;
         if (goHnd<>0) then
         pobj := _unit_GetTObj(goHnd);
         var wounded : Boolean;
         var bProcessLogic : Boolean = ((not _net_IsReplay) and _net_IsOffline) or _net_IsServer or _net_IsRecord;

         var bPeaceSkip : Boolean;
         if gbool_peacemode then
         begin
            bPeaceSkip := True;
            if (bProcessLogic) then
            begin
               if (pobj<>nil) then
               begin
                  var myEnemy : Boolean = _unit_IsOnEnemyTerritory(goHnd, pObj);
                  var trgEnemy : Boolean = _unit_IsOnEnemyTerritory(trgHnd, pObj2);

                  if (not myEnemy) and (not trgEnemy) then
                  begin
                     TObj(pObj).hp := 0;
                     bSkipOnDeath := True;
                  end
                  else
                  if myEnemy and trgEnemy then
                  bPeaceSkip := False
                  else
                  begin
                     if myEnemy then
                     begin
                        TObj(pObj).hp := 0;
                        bSkipOnDeath := True;
                     end;

                     if trgEnemy then
                     TObj(pObj2).hp := 0;
                  end;
               end;
            end;
         end;
         if not bPeaceSkip then
         begin
            var pobjbase2 : Pointer = gPlayer[TObj(pobj2).pl].objbase[TObj(pobj2).cid][TObj(pobj2).id];
            var pobjprop2 : Pointer = gObjProp[TObj(pobj2).cid][TObj(pobj2).id];
            
            var damage : Integer = indamage;

            var bCanHeadShot : Boolean = ((weapkind=gc_obj_weapon_kind_arrow) or (weapkind=gc_obj_weapon_kind_bullet)) and (not TObjProp(pobjprop2).bbuilding);
            var bFastHorseBullet : Boolean = bCanHeadShot and (TObjProp(pobjprop2).usage=gc_obj_usage_fasthorse) and ((GetGameObjectStatesTagByHandle(trgHnd) and gc_statetag_move_walk)<>0);
            if (bFastHorseBullet) then
            damage := damage-5;

            if (TObj(pobj2).bbuilt) then
            damage := damage-TObjBase(pobjbase2).shield
            else
            damage := damage-TObjBase(pobjbase2).shield div 3;
            var pSquad2 : Pointer = nil;
            if (TObj(pobj2).squad>=0) then
            pSquad2 := gPlayer[TObj(pobj2).pl].squads.Get(TObj(pobj2).squad);

            if (pSquad2 <> nil) then
            begin
               var bonus : Integer;
               if (TSquad(pSquad2).fHoldMode) then
               bonus := TSquad(pSquad2).fAddShieldHold
               else
               bonus := TSquad(pSquad2).fAddShield;
               damage := damage-bonus;
            end;
            if (goHnd<>0) then
            begin
               if (pobj<>nil) then
               begin
                  var pobjprop : Pointer = gObjProp[TObj(pobj).cid][TObj(pobj).id];

                  if (not TObjProp(pobjprop).bpriest) then
                  begin
                     var plIOInd : Integer = GetPlayerIndexByHandle(GetPlayerHandleInterfaceIO);
                     if (TObj(pobj2).pl=plIOInd) or ((pobj<>nil) and (TObj(pobj).pl=plIOInd)) then
                     _misc_DoAlarm(goHnd, trgHnd, gc_gui_alarmevent_attack);
                  end;

                  if (not bProcessLogic) then
                  begin
                     if (TObjProp(pobjprop).bpriest) then
                     begin
                        damage := indamage;
                        TObj(pobj2).hp := TObj(pobj2).hp+damage;
                        if (TObj(pobj2).hp>=TObjBase(pobjbase2).maxhp) then
                        TObj(pobj2).hp := TObjBase(pobjbase2).maxhp;
                     end
                     else
                     begin
                        damage := damage-TObjBase(pobjbase2).protection[weapkind];
                        if (damage<1) then damage := 1;
                        if (damage<TObj(pobj2).hp) then // not critical hit for not bprocesslogic
                        TObj(pobj2).hp := TObj(pobj2).hp-damage;
                     end;
                  end
                  else
                  begin
                     if (TObjProp(pobjprop).bpriest) then
                     begin
                        damage := indamage;
                        TObj(pobj2).hp := TObj(pobj2).hp+damage;
                        if (TObj(pobj2).hp>=TObjBase(pobjbase2).maxhp) then
                        begin
                           TObj(pobj2).hp := TObjBase(pobjbase2).maxhp;
                           _unit_SetSTO(goHnd, 0);
                           _unit_RemoveOrder(goHnd, 0);
                           _unit_SetTagStates(goHnd, gc_statetag_execute_none or gc_statetag_move_idle or gc_statetag_action_none);
                        end;
                     end
                     else
                     begin
                        var squad : Integer = TObj(pObj).squad;
                        wounded := true;
                        var plInd : Integer = TObj(pobj).pl;

                        if gPlayer[plInd].bAI and (squad >= 0) and (not TObjProp(pObjProp).bArtillery) then
                        begin
                           var pSquad : Pointer = gPlayer[plInd].squads.Get(squad);
                           if (pSquad<>nil) and (TSquad(pSquad).fArmy >= 0) then
                           begin
                              var pArmy : Pointer = gPlayer[plInd].aiData.armyList.Get(TSquad(pSquad).fArmy);
                              if (pArmy <> nil) and (not _unit_IsArtillery(goHnd)) then
                              begin
                                 TSquad(pSquad).fAgressive := True;
                                 TArmy(pArmy).fLastBattleTime := GetGameTime;
                              end;
                           end;
                        end;

                        var bHeadShot : Boolean = bCanHeadShot and (random<0.05) and (not bFastHorseBullet); // in C1 there is 4 percent chance to kill any unit with bullet, no matter how much hp. in c3 after shooters rebalance. changed change to 2 percent
                        if (squad>=0) and (weapkind<>gc_obj_weapon_kind_firearrow) then
                        begin
                           //if (weapInd=0) then
                           //begin
                              var bonus : Integer;
                              var pSquad : Pointer = gPlayer[plInd].squads.Get(squad);
                              if (TSquad(pSquad).fHoldMode) {and (weapkind<>gc_obj_weapon_kind_bullet)} then
                              bonus := TSquad(psquad).fAddDamageHold
                              else
                              bonus := TSquad(psquad).fAddDamage;
                              damage := damage+bonus;
                           //end;
                        end;
                        damage := damage-TObjBase(pobjbase2).protection[weapkind];

                        if (bHeadShot) then
                        damage := damage+floor(TObj(pobj).uniqrnd*500);

                        if (damage<1) then damage := 1;
                        TObj(pobj2).hp := TObj(pobj2).hp-damage;
                        //log('go='+IntToStr(goHnd)+' doo '+IntToStr(damage)+' damage too trg='+IntToStr(trgHnd));

                        if (TObj(pobj2).hp<=0) then
                        begin
                           gPlayer[plInd].counter.scores := gPlayer[plInd].counter.scores+TObjProp(pobjprop2).score*2;

                           var pObjInside : Pointer = _misc_GetObjectArgData(trgHnd, gc_argunit_inside);
                           if pObjInside <> nil then
                           begin
                              var i : Integer;
                              for i:=TIntegerList(pObjInside).GetCount-1 downto 0 do
                              begin
                                 var insideHnd : Integer = TIntegerList(pObjInside).Get(i);
                                 if (insideHnd<>0) then
                                 begin
                                    var pinsideobj : Pointer = _unit_GetTObj(insideHnd);
                                    if (pinsideobj<>nil) then
                                    gPlayer[plInd].counter.scores := gPlayer[plInd].counter.scores+gObjProp[TObj(pinsideobj).cid][TObj(pinsideobj).id].score*3;
                                 end;
                              end;
                           end;

                           TObj(pobj).kill := TObj(pobj).kill+1;
                        end;
                     end;
                     var trgSTO : Integer = GetGameObjectSTOHandleByHandle(trgHnd);
                     var bTrgIsResOrMyBld : Boolean;
                     if trgSTO<>0 then
                     begin
                        case _misc_GetBaseObjID(trgSTO) of
                           gc_baseid_obj :
                           begin
                              var ptrgstoobj : Pointer = _unit_GetTObj(trgSTO);
                              bTrgIsResOrMyBld := gObjProp[TObj(ptrgstoobj).cid][TObj(ptrgstoobj).id].bbuilding and (TObj(ptrgstoobj).pl=TObj(pobj2).pl);
                           end;
                           gc_baseid_res : bTrgIsResOrMyBld := True;
                        end;
                     end;
                     var sEnemy : Boolean = TObj(pobj2).bsearchenemy;
                     if (((trgSTO=0) or bTrgIsResOrMyBld) and (TObj(pobj2).hp>0) and ((gPlayer[TObj(pObj).pl].enemyPlMask) and (gPlayer[TObj(pobj2).pl].myplmask)<>0) and (_unit_CanKill(trgHnd, goHnd)))
                     and ((TObj(pobj2).bsearchenemy) and (not TObjProp(pobjprop2).bpriest))
                     and (
                     ((TObj(pobj2).orders[0].itype=gc_obj_order_type_none) or ((TObj(pobj2).orders[0].itype=gc_obj_order_type_move) and (TObj(pobj2).orders[0].info.progress=gc_obj_order_move_mode_attack)))
                     or ((not bCanHeadShot) and bTrgIsResOrMyBld and (TObj(pobj2).orders[0].itype<>gc_obj_order_type_move) and(TObj(pobj2).orders[0].itype<>gc_obj_order_type_attackobj))
                     ) then
                     begin
                        if TObj(pobj2).hp<=0 then
                        ErrorLog('ACHTUNG : i can hang');
                        if ((not gPlayer[TObj(pobj2).pl].bAI) or _ai_CanUnitAttack(trgHnd))
                        and _unit_SameRegionExt(goHnd, trgHnd) then
                        begin
                           if bTrgIsResOrMyBld then
                           _unit_SetTagStates(trgHnd, gc_statetag_action_none);
                           _unit_OrderAttack(trgHnd, goHnd, True, False, False);
                        end;
                     end;
                  end;
               end;
            end
            else
            //if (goHnd=0) then
            begin
               if (damage<1) then damage := 1;
               if (bProcessLogic) or (damage<TObj(pobj2).hp) then // not critical hit for not bprocesslogic
               TObj(pobj2).hp := TObj(pobj2).hp-damage;
               wounded := true;
            end;
         end;

         if (bProcessLogic) then
         begin
            if (not bSkipOnDeath) and (pobj<>nil) and (TObj(pobj).hp<=0) then
            begin
               if (not TObj(pobj).bdead) then
               GameObjectExecuteStateByHandle(goHnd, 'OnDeath');
               _unit_SetTagStates(goHnd, gc_statetag_essential_death);
            end;

            if (TObj(pobj2).hp<=0) then
            begin
               if (weapkind=gc_obj_weapon_kind_firearrow) and (gObjProp[TObj(pobj2).cid][TObj(pobj2).id].bbuilding) then
               begin
                  var plIOInd : Integer = GetPlayerIndexInterfaceIO;
                  if TObj(pObj).pl = plIOInd then
                  begin
                     gProfileUserStruct.achs.ach[gc_ach_destroy_buildings10].cur:=Min(gProfileUserStruct.achs.ach[gc_ach_destroy_buildings10].cur+1, 10);
                     gProfileUserStruct.achs.ach[gc_ach_destroy_buildings100].cur:=Min(gProfileUserStruct.achs.ach[gc_ach_destroy_buildings100].cur+1, 100);
                  end;
               end;
               GameObjectExecuteStateByHandle(trgHnd, 'OnDeath');
               _unit_SetTagStates(trgHnd, gc_statetag_essential_death);
               var usage : Integer = gObjProp[TObj(pobj2).cid][TObj(pobj2).id].usage;
               if (gObjProp[TObj(pobj2).cid][TObj(pobj2).id].bbuilding) and ((usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall)) then
               _misc_DoWallDamage(trgHnd, gc_wall_damage_kill);
            end;

            var stoHnd : Integer = GetGameObjectSTOHandleByHandle(trgHnd);
            if wounded and (stoHnd = 0) and gPlayer[TObj(pObj2).pl].bAI and _misc_IsProcessAI and (TObj(pObj2).squad >= 0) then
            begin
               var plInd : Integer = TObj(pObj2).pl;
               var sqInd : Integer = TObj(pObj2).squad;
               var pSquad : Pointer = gPlayer[plInd].squads.Get(sqInd);
               if (pSquad <> nil) and (TSquad(pSquad).fArmy >= 0) then
               begin
                  var pArmy : Pointer = gPlayer[plInd].aiData.armyList.Get(TSquad(pSquad).fArmy);
                  if (pArmy<>nil) and (_ai_ArmyCanAttack(pArmy)) and (TArmy(pArmy).fOrder.iType <> gc_ai_armyorder_bitva) then
                  begin
                     _unit_OrderAttack(trgHnd, goHnd, True, false, False);
                     var tX, tY, tZ : Float;
                     GetGameObjectAbsolutePositionByHandle(goHnd, tX, tY, tZ);
                     _ai_ArmyBitva(pArmy, tX, tZ);
                  end;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_DoRoundDamage
//
function _misc_DoRoundDamage(x, y, r : Float; damage, owner, trg, weaponkind : Integer; bsimulation : Boolean) : Integer;
begin
   Result := gc_obj_explmedia_default;
   const cMaxExplMediaR = 225/gc_pixels_to_tile;
   gDoRoundDamageList.Clear;
   var barrow : Boolean = weaponkind=gc_obj_weapon_kind_arrow;
   if bsimulation then
   _misc_MakeListObjectsInRadius(x, y, r, false, 0, 0, True, gDoRoundDamageList)
   else
   if barrow then
   _misc_MakeListObjectsInRadius(x, y, r, false, 0, 0, False, gDoRoundDamageList)
   else
   _misc_MakeListObjectsInRadius(x, y, r+cMaxExplMediaR, false, 0, 0, True, gDoRoundDamageList);
   var dmgHnd : Integer;
   var count : Integer = Floor(1+sqr(r/0.35));
   var damaged : Integer;
   var listcount : Integer = gDoRoundDamageList.GetCount;
   var i : Integer;
   for i:=listcount-1 downto 0 do
   begin
      dmgHnd := gDoRoundDamageList.Get(i);
      if (dmgHnd<>0) and (owner<>dmgHnd) then
      begin
         var pobj : Pointer = _unit_GetTObj(dmgHnd);
         if (pobj<>nil) then
         begin
            var pobjprop : Pointer = gObjProp[TObj(pobj).cid][TObj(pobj).id];
            if not TObjProp(pobjprop).bbuilding and ((not barrow and (VectorDistance(x, 0, y, GetGameObjectPositionXByHandle(dmgHnd), 0, GetGameObjectPositionZByHandle(dmgHnd))<(r+TObjProp(pobjprop).explradius))) or (barrow and ((listcount>300) or (VectorDistance(x, 0, y, GetGameObjectPositionXByHandle(dmgHnd), 0, GetGameObjectPositionZByHandle(dmgHnd))<r)))) then
            begin
               if not barrow and (TObjProp(pobjprop).explmedia<>gc_obj_explmedia_default) then
               Result := TObjProp(pobjprop).explmedia;
               if trg<>dmgHnd then
               begin
                  _misc_DoDamage(owner, dmgHnd, damage, -1, weaponkind);
                  damaged := damaged+1;
                  if damaged>=count then // prevent too much damage when units packed closly in one point
                  break;
               end;
            end;
         end;
      end;
   end;
end;

// _misc_ProjSimulatePointsProgress
//
procedure _misc_ProjSimulatePointsProgress();
begin
   //if (gProjSimulatePoints.maxdamagetime>0) then // done outside
   //begin
      //_misc_ProfilerBegin('_misc_ProjSimulatePointsProgress');
      var bMaxDamageTimeExists : Boolean;
      var gametime : Float = GetGameTime;
      var i : Integer;
      for i:=0 to gc_MaxProjSimulatePoints-1 do
      begin
         if (gProjSimulatePoints.projsim[i].weaponid>0) then
         begin
            if (gametime>=gProjSimulatePoints.projsim[i].damagetime) then
            begin
               _misc_DoRoundDamage(gProjSimulatePoints.projsim[i].px, gProjSimulatePoints.projsim[i].pz, gWeapons[gProjSimulatePoints.projsim[i].weaponid].radius, gWeapons[gProjSimulatePoints.projsim[i].weaponid].damage, GetGameObjectHandleByUniqueId(gProjSimulatePoints.projsim[i].owner), 0, 0, True);
               gProjSimulatePoints.projsim[i].weaponid := 0;
               gProjSimulatePoints.projsim[i].owner := 0;
               gProjSimulatePoints.projsim[i].px := 0;
               gProjSimulatePoints.projsim[i].pz := 0;
               gProjSimulatePoints.projsim[i].damagetime := 0;
            end
            else
            bMaxDamageTimeExists := True;
         end;
      end;
      if (not bMaxDamageTimeExists) then
      gProjSimulatePoints.maxdamagetime := 0;
      //_misc_ProfilerEnd('_misc_ProjSimulatePointsProgress');
   //end;
end;

// _misc_ProjSimulatePoint
//
procedure _misc_ProjSimulatePoint(const newind, weaponid, owner : Integer; const mx, my, mz : Float);
begin
   //_misc_ProfilerBegin('sim');
   var seed : Integer = floor(RandomExt*100000);

   function Uniform(var seed : Integer) : Float;
   begin
      const a = 314159269;
      const c = 453806245;
      const m = 2147483647;
      seed := a*seed+c;
      seed := seed mod m;
      Result := seed/m;
   end;

   function RandomNormalDistribution(meanN, stndrdDev : Float; var seed : Integer) : Float;
   begin
      var s, y, v1, v2 : Float;
      repeat
         v1 := 2*Uniform(seed)-1;
         v2 := 2*Uniform(seed)-1;
         s := v1*v1+v2*v2;
      until (s<1);
      y := v2*sqrt(-2*Ln(s)/s);
      Result := meanN+stndrdDev*y;
   end;

   function RandomGauss() : Float;
   begin
      var localseed : Integer = floor(RandomExt*1000000);
      const stndrdDev = 0.25;
      const min = 0.000001;
      const max = 0.999999;
      var s, y, v1, v2 : Float;
      repeat
         v1 := 2*Uniform(localseed)-1;
         v2 := 2*Uniform(localseed)-1;
         s := v1*v1+v2*v2;
      until (s<1);
      y := v2*sqrt(-2*Ln(s)/s);
      Result := Clamp(stndrdDev*y/2+0.5, min, max);
   end;
   function RandomGaussExt(stndrdDev, offset, min, max : Float) : Float;
   begin
      var localseed : Integer = floor(RandomExt*1000000);
      var s, y, v1, v2 : Float;
      repeat
         v1 := 2*Uniform(localseed)-1;
         v2 := 2*Uniform(localseed)-1;
         s := v1*v1+v2*v2;
      until (s<1);
      y := v2*sqrt(-2*Ln(s)/s);
      Result := Clamp(offset+stndrdDev*y/2, min, max);
   end;

   var ind : Integer = newind;
   if (ind>(gc_MaxProjSimulatePoints-1)) then
   ind := ind mod (gc_MaxProjSimulatePoints-1);

   if (gProjSimulatePoints.projsim[ind].weaponid>0) then
   _misc_DoRoundDamage(gProjSimulatePoints.projsim[ind].px, gProjSimulatePoints.projsim[ind].pz, gWeapons[gProjSimulatePoints.projsim[ind].weaponid].radius, gWeapons[gProjSimulatePoints.projsim[ind].weaponid].damage, GetGameObjectHandleByUniqueId(gProjSimulatePoints.projsim[ind].owner), 0, 0, True);

   const mintime = 0.275;
   const maxtime = 0.64;
   const mindst = 0;
   const maxdst = 1.3;
   var rnd : Float = (0.36+RandomGaussExt(0.425, 0, -2, 2)); //0.5
   if (rnd<0) then rnd := 1+rnd
   else
   if (rnd>1) then rnd := 1-(rnd-1);
   var dst : Float = rnd*maxdst;
   var newmaxtime : Float = mintime+(maxtime-mintime)*0.5+(maxtime-mintime)*rnd*0.5;
   var timedelta : Float = rnd*(newmaxtime-mintime);
   var damagetime : Float = mintime+timedelta+RandomExt*(newmaxtime-mintime-timedelta);

   var dx : Float = dst;
   var dy, dz : Float;
   VectorRotateY(dx, dy, dz, RandomExt*360);
   gProjSimulatePoints.projsim[ind].weaponid := weaponid;
   gProjSimulatePoints.projsim[ind].owner := owner;
   gProjSimulatePoints.projsim[ind].px := mx+dx;
   gProjSimulatePoints.projsim[ind].pz := mz+dz;
   gProjSimulatePoints.projsim[ind].damagetime := GetGameTime+damagetime;
   if (gProjSimulatePoints.projsim[ind].damagetime>gProjSimulatePoints.maxdamagetime) then
   gProjSimulatePoints.maxdamagetime := MaxFloat(gProjSimulatePoints.maxdamagetime, gProjSimulatePoints.projsim[ind].damagetime);
   gProjSimulatePoints.curind := ind+1;
   //_misc_ProfilerEnd('sim');
end;

// _misc_CheckStringValidBool
//
procedure _misc_CheckStringValidBool(var str : String; var bvalid, bstate : Boolean);
begin
   bvalid := True;
   if (SameText(str, 'on')) or (SameText(str, 'true')) then
   bstate := True
   else
   if (SameText(str, 'off')) or (SameText(str, 'false')) then
   bstate := False
   else
   begin
      bvalid := False;
      bstate := False;
   end;
end;

// _misc_PreloadTextures
//
procedure _misc_PreloadTextures(const bloadexisted, bloadproduce, bshowprogressbar : Boolean);
begin
   if (GetCountOfPlayers>=gc_MaxPlayerCount) then
   begin
      var bUnitExistsArr : array [0..gc_MaxCountryCount-1] of array [0..gc_country_maxmembers-1] of Boolean;
      var arrCID : array [0..gc_MaxCountryCount-1] of Boolean;
      const cStringArrayMaxCount = gc_MaxPlayerCount*gc_country_maxmembers;
      var sArrLoadLodActorsAndMaterialsRaceName : array [0..cStringArrayMaxCount-1] of String;
      var sArrLoadLodActorsAndMaterialsBaseName : array [0..cStringArrayMaxCount-1] of String;
      var curIndLoadLOD : Integer = 0;
      procedure AddToStringArray(const race, base : String);
      begin
         if (curIndLoadLOD>cStringArrayMaxCount-1) then
         begin
            ErrorLog('_misc_PreloadTextures : AddToStringArray out of bounds');
            exit;
         end;
         sArrLoadLodActorsAndMaterialsRaceName[curIndLoadLOD] := race;
         sArrLoadLodActorsAndMaterialsBaseName[curIndLoadLOD] := base;
         curIndLoadLOD := curIndLoadLOD+1;
      end;

      ProjectLoadPFXMaterial('');
      ProjectLoadDecalMaterial('envfieldd');
      ProjectLoadDecalMaterial('groundexp0');
      ProjectLoadDecalMaterial('groundexp1');
      ProjectLoadDecalMaterial('groundexp2');
      ProjectLoadDecalMaterial('groundexp3');
      AddToStringArray(gc_racename_env, 'field');
      AddToStringArray(gc_racename_misc, 'debris_stone');
      AddToStringArray(gc_racename_misc, 'debris_wood');
      AddToStringArray(gc_racename_misc, 'projarrow');
      AddToStringArray(gc_racename_misc, 'projcannonball');
      AddToStringArray(gc_racename_misc, 'projgrenade');
      AddToStringArray(gc_racename_misc, 'projhowitzerball');
      AddToStringArray(gc_racename_misc, 'projhowitzerballfragment');
      AddToStringArray(gc_racename_misc, 'projmortarball');
      AddToStringArray(gc_racename_misc, 'movearrow');
      AddToStringArray(gc_racename_misc, 'rallypoint');

      if (GetCountOfPlayers>=gc_MaxPlayerCount) then
      begin
         var totalphys, availphys, totalvirt, availvirt : Integer;
         GetHardwareRAMInfo(totalphys, availphys, totalvirt, availvirt);
         var ramGB : Integer = round(totalphys/1024) div 1000;
         var megaPC : Boolean = ramGB>=8;
         var lowPC : Boolean = ramGB<=2;

         var bMineExists : Boolean;
         var i, j, k : Integer;
         if (gMap.settings.additional.startingunits<>gc_mapsettings_startingunits_differentnations) then
         begin
            for i:=0 to gc_MaxPlayerCount-1 do
            begin
               var plHnd : Integer = GetPlayerHandleByIndex(i);
               for j:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
               begin
                  var goHnd : Integer = GetGameObjectHandleByIndex(j, plHnd);
                  var pobj : Pointer = _unit_GetTObj(goHnd);
                  if (pobj<>nil) then
                  begin
                     var id : Integer = TObj(pobj).id;
                     var cid : Integer = TObj(pobj).cid;
                     if (not bUnitExistsArr[cid][id]) then
                     begin
                        if (gObjProp[cid][id].usage=gc_obj_usage_peasant) then
                        begin
                           bUnitExistsArr[cid][id] := True;
                           if (bloadproduce) then
                           begin
                              var fixedproduceInd : Integer = _country_GetFixedProduceIndexBySID(cid, gObjProp[cid][id].sid, False);
                              if (fixedproduceInd<>-1) then
                              begin
                                 for k:=0 to gc_country_fixedproduce_maxcount-1 do
                                 begin
                                    var produceSID : String = gCountry[cid].fixedproduce[fixedproduceInd].build[k].id;
                                    if (produceSID<>'') then
                                    begin
                                       var bldid : Integer = _unit_ConvertObjSIDToID(cid, produceSID);
                                       var bskip : Boolean;
                                       if (not megaPC) then
                                       begin
                                          var tmp : String = SubStr(produceSID, 4, StrLength(produceSID)-3);
                                          case tmp of
                                             'tow', 'swa', 'wwa', 'art', 'por'{, 'sta'}, 'tem', 'ba2' : bskip := True;
                                          end;
                                          if (lowPC) then
                                          case tmp of
                                             'sta', 'mar', 'aca', 'dip', 'hou' : bskip := True;
                                          end;
                                       end;
                                       if (not bskip) then
                                       begin
                                          if (not bMineExists) and (gObjProp[cid][bldid].usage=gc_obj_usage_mine) then
                                          bMineExists := True;
                                          bUnitExistsArr[cid][bldid] := True;
                                       end;
                                    end;
                                 end;
                              end;
                           end;
                        end;
                        if (bloadexisted) then
                        bUnitExistsArr[cid][id] := True;
                     end;
                  end;
               end;
            end;
         end;
         var sid : String;
         var cid, id : Integer;
         if (bMineExists) then
         begin
            sid := 'eurcoa';
            id := _unit_ConvertObjSIDToID(0, sid); // cid=0 any nation is fine
            bUnitExistsArr[0][id] := True;
            sid := 'euriro';
            id := _unit_ConvertObjSIDToID(0, sid);
            bUnitExistsArr[0][id] := True;
         end;
         for cid:=0 to gc_MaxCountryCount-1 do
         for id:=0 to gc_country_maxmembers-1 do
         if (bUnitExistsArr[cid][id]) then
         begin
            sid := gObjProp[cid][id].sid;
            if (gObjProp[cid][id].bbuilding) then
            begin
               AddToStringArray(gc_racename_buildings, sid);
               AddToStringArray(gc_racename_buildings, sid+'a');
               AddToStringArray(gc_racename_buildings, sid+'anim');
               ProjectLoadDecalMaterial(sid+'d');
            end
            else
            AddToStringArray(gc_racename_units, sid);
         end;
         if (bshowprogressbar) then
         _gui_ProcessProgressBar(GetLocaleTableListItemByID('misc', 'progressbar.loadingtextures'), 0, curIndLoadLOD, True);
         var lastpercent : Integer = -1;
         for i:=0 to curIndLoadLOD-1 do
         begin
            ProjectLoadLodActorsAndMaterials(sArrLoadLodActorsAndMaterialsRaceName[i], sArrLoadLodActorsAndMaterialsBaseName[i]);
            if (bshowprogressbar) then
            begin
               var newpercent : Integer = floor(100*i/curIndLoadLOD);
               if (lastpercent<>newpercent) then
               _gui_ProcessProgressBar(GetLocaleTableListItemByID('misc', 'progressbar.loadingtextures'), i, curIndLoadLOD, True);
               lastpercent := newpercent;
            end;
         end;
      end;
   end;
end;

// _misc_PrepareWallClusterForLan
//
procedure _misc_PrepareWallClusterForLan(var wallcluster : TWallCluster);
begin
   var miscplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   if (miscplhnd<>0) then
   begin
      var c : Integer = wallcluster.Cells.GetCount;
      var i : Integer;
      for i:=0 to c-1 do
      begin
         var p : Pointer = wallcluster.Cells.GetPointer(i);
         var sid : string;
         _misc_GetSIDBySprite(wallcluster.wallType, TWallCell(p).GetSprite, wallcluster.cid, sid);
         var px : Float = floor(TWallCell(p).x);
         var py : Float = floor(TWallCell(p).y);

         var goHnd : Integer = TWallCell(p).goHnd;
         if (goHnd=0) then
         begin
            goHnd := CreatePlayerGameObjectHandleByHandle(miscplhnd, gc_racename_buildings, sid, px+0.5, 0, py+0.5);
            SetGameObjectColIntMaskOptionsByHandle(goHnd, True, False);
            TWallCell(p).goHnd := goHnd;
         end;
         SetGameObjectPositionByHandle(goHnd, px+0.5, RayCastHeight(px+0.5, py+0.5), py+0.5);
      end;
   end;
end;

// _misc_UpdateWall
//
procedure _misc_UpdateWall(var wallcluster : TWallCluster);
begin
   var miscplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
   if (miscplhnd<>0) then
   begin
      var c : Integer = wallcluster.Cells.GetCount;
      var i : Integer;
      for i:=0 to c-1 do
      begin
         var p : Pointer = wallcluster.Cells.GetPointer(i);
         var sid : string;
         _misc_GetSIDBySprite(wallcluster.wallType, TWallCell(p).GetSprite, wallcluster.cid, sid);
         var px : Float = floor(TWallCell(p).x);
         var py : Float = floor(TWallCell(p).y);

         if _misc_IsPlayerWallExists(-1, px+0.5, py+0.5) then
         continue;

         var goHnd : Integer = TWallCell(p).goHnd;
         if (goHnd=0) then
         begin
            goHnd := CreatePlayerGameObjectHandleByHandle(miscplhnd, gc_racename_buildings, sid, px+0.5, 0, py+0.5);
            SetGameObjectColIntMaskOptionsByHandle(goHnd, True, False);
            TWallCell(p).goHnd := goHnd;
         end;
         SetGameObjectPositionByHandle(goHnd, px+0.5, RayCastHeight(px+0.5, py+0.5), py+0.5);

         var arrx : Integer = floor(px+gc_MaxMapWidth div 2);
         var arry : Integer = floor(py+gc_MaxMapHeight div 2);
         gCanPlaceBuildingWalls[arrx][arry].bwall := True;
         gCanPlaceBuildingWalls[arrx][arry].bwallchecked := True;

         {var blinkmode : Integer;
         if (_misc_CanPlaceBuilding(goHnd, wallcluster.plIndex, wallcluster.wallType, px+0.5, py+0.5, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist)) then
         blinkmode := gc_gui_blinkmode_canbuild
         else
         blinkmode := gc_gui_blinkmode_cannotbuild;

         _unit_DoConstructionBlinkGameObject(goHnd, blinkmode, False, False);}
         _unit_DoConstructionBlinkGameObject(goHnd, gc_gui_blinkmode_canbuild, False, False);
      end;
   end;
end;

// _misc_GetAdjustedBuildingPos
//
function _misc_GetAdjustedBuildingPos(const goHnd, plInd, usage : Integer; const inX, inZ : Float; var outX, outZ : Float) : boolean;
begin
   Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, inX, inZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True);
   if Result then
   begin
      outX := inX;
      outZ := inZ;
   end
   else
   begin
      var tmpX, tmpZ : Float;
      tmpX := inx-0.5; tmpZ := inz; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+0.5; tmpZ := inz; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx; tmpZ := inz-0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx; tmpZ := inz+0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;

      tmpX := inx-0.5; tmpZ := inz-0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx-0.5; tmpZ := inz+0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+0.5; tmpZ := inz-0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+0.5; tmpZ := inz+0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;

      tmpX := inx-1; tmpZ := inz; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+1; tmpZ := inz; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx; tmpZ := inz-1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx; tmpZ := inz+1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;

      tmpX := inx-1; tmpZ := inz-0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx-1; tmpZ := inz+0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx-0.5; tmpZ := inz-1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+0.5; tmpZ := inz-1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+1; tmpZ := inz-0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+1; tmpZ := inz+0.5; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx-0.5; tmpZ := inz+1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+0.5; tmpZ := inz+1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;

      tmpX := inx-1; tmpZ := inz-1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx-1; tmpZ := inz+1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+1; tmpZ := inz-1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
      tmpX := inx+1; tmpZ := inz+1; Result := _misc_CanPlaceBuilding(goHnd, plInd, usage, tmpX, tmpZ, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True); if Result then begin outX := tmpX; outZ := tmpZ; exit; end;
   end;
end;

// _misc_GUIProgressPickedBldUID
//
procedure _misc_GUIProgressPickedBldUID();
begin
   if (gint_gui_pickedbldhnd<>0) then
   begin
      var mx, my, mz : Float;
      GetCurrentMouseWorldCoord(mx, my, mz);
      _misc_NormalizePosToMap(mx, mz);
      mx := floor(mx*2)/2+0.25;
      mz := floor(mz*2)/2+0.25;

      var plInd : Integer = GetPlayerIndexByHandle(GetPlayerHandleInterfaceIO);
      var goHnd : Integer = gint_gui_pickedbldhnd;
      var basename : String = GetGameObjectBaseNameByHandle(goHnd);
      var blinkMode : Integer;
      var bavailable : Boolean;

      var tag : Integer = round(GetGameObjectTagFloatByHandle(goHnd));
      var cid : Integer = tag div 256;
      var id : Integer = tag mod 256;
      var usage : Integer = gObjProp[cid][id].usage;
      if (usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall) then
      begin
         var bfilled : Boolean;
         if (not gWallCluster.FirstWall) or (gWallCluster.Cells.GetCount=0) then
         begin
            var p : Pointer = gCanPlaceBuildingWalls;
            if (p<>nil) then
            FillMem(p, SizeOf(gCanPlaceBuildingWalls), 0);
         end;

         if (not gWallCluster.FirstWall) then
         begin
            var wx, wz : Integer;
            if (gWallCluster.finalX<>floor(mx)) or (gWallCluster.finalY<>floor(mz)) then
            begin
               //if (gWallCluster.cornPt.getCount>1) then
               gWallCluster.UndoSegment();
               gWallCluster.ConnectToPoint(floor(mx), floor(mz));
               //gWallCluster.KeepSegment();
               gWallCluster.CreateSprites();
               _misc_UpdateWall(gWallCluster);
            end;
            //updating on can place:
            var i : Integer;
            for i:=0 to gWallCluster.Cells.GetCount-1 do
            begin
               var p : Pointer = gWallCluster.Cells.GetPointer(i);
               var goHnd : Integer = TWallCell(p).goHnd;
               if (goHnd<>0) then
               begin
                  var posx : Float = GetGameObjectPositionXByHandle(goHnd);
                  var posz : Float = GetGameObjectPositionZByHandle(goHnd);
                  if _misc_CanPlaceBuilding(goHnd, plind, usage, posx, posz, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True) then
                  _unit_DoConstructionBlinkGameObject(goHnd, gc_gui_blinkmode_canbuild, False, True)
                  else
                  _unit_DoConstructionBlinkGameObject(goHnd, gc_gui_blinkmode_cannotbuild, False, True);
               end;
            end;
         end;
         if gWallCluster.Cells.GetCount=0 then
         begin
            SetGameObjectVisibleByHandle(gint_gui_pickedbldhnd, true);
            SetGameObjectPositionByHandle(goHnd, floor(mx)+0.5, my, floor(mz)+0.5);
            bavailable := _misc_CanPlaceBuilding(goHnd, plInd, usage, floor(mx)+0.5, floor(mz)+0.5, gc_path_mincoldist, 0.26, 0.5, gc_path_minemincoldist, True);
            if (bavailable) then
            blinkMode := gc_gui_blinkmode_canbuild
            else
            blinkMode := gc_gui_blinkmode_cannotbuild;
            _unit_DoConstructionBlinkGameObject(goHnd, blinkMode, False, True);
         end
         else
         SetGameObjectVisibleByHandle(gint_gui_pickedbldhnd, false);
      end
      else
      begin
         if (usage=gc_obj_usage_mine) then
         begin
            var restype, reshnd : Integer;
            var px, py, pz : Float;
            _unit_FindMinePositionAndType(mx, mz, gc_gameplay_findminepositionradius, restype, reshnd, px, pz);
            if (reshnd<>0) then
            begin
               {if gbool_peacemode then
               begin
                  var gridx, gridy : Integer;
                  _misc_PosToScanGridIndices(mx, mz, gridx, gridy);
                  if _misc_IsCorrectScanCellOwner(plInd, gridx, gridy) then
                  begin
                     bavailable := _misc_CanPlaceBuilding(goHnd, plInd, usage, px, pz, 0, 0.26, 0, -1, True);
                     py := GetGameObjectPositionYByHandle(reshnd);
                     SetGameObjectPositionByHandle(goHnd, px, py, pz);
                  end
                  else
                  begin
                     bavailable := False;
                     SetGameObjectPositionByHandle(goHnd, mx, my, mz);
                  end;
               end
               else}
               begin
                  bavailable := _misc_CanPlaceBuilding(goHnd, plInd, usage, px, pz, 0, 0.26, 0, -1, True);
                  py := GetGameObjectPositionYByHandle(reshnd);
                  SetGameObjectPositionByHandle(goHnd, px, py, pz);
               end;
            end
            else
            begin
               bavailable := False;
               SetGameObjectPositionByHandle(goHnd, mx, my, mz);
            end;
         end
         else
         begin
            // normalize position to collision grid size
            bavailable := _misc_GetAdjustedBuildingPos(goHnd, plInd, usage, mx, mz, mx, mz);
            if not bavailable then
            GetCurrentMouseWorldCoord(mx, my, mz); // experiment
            SetGameObjectPositionByHandle(goHnd, mx, my, mz);
            if (usage=gc_obj_usage_port) then
            begin
               if (GetGameObjectCountChildByHandle(goHnd)>0) then
               begin
                  if (bavailable) then
                  begin
                     var tmpx, tmpz : Float;
                     if (_misc_GetPortResourcePoint(goHnd, tmpx, tmpz, True)) then
                     begin
                        var i : Integer;
                        for i:=0 to 3 do
                        begin
                           var dirx : Float = 3;
                           var diry, dirz : Float;
                           VectorRotateY(dirx, diry, dirz, i*90);
                           var buiHnd : Integer = GetGameObjectGOHandleChildByHandle(goHnd, i);
                           SetGameObjectAbsolutePositionByHandle(buiHnd, tmpx+dirx, 0, tmpz+dirz);
                        end;
                     end;
                  end
                  else
                  begin
                     var i : Integer;
                     for i:=GetGameObjectCountChildByHandle(goHnd)-1 downto 0 do
                     begin
                        var child : Integer = GetGameObjectGOHandleChildByHandle(goHnd, i);
                        GameObjectDestroyByHandle(child);
                     end;
                  end;
               end
               else
               if (bavailable) then
               begin
                  var tmpx, tmpz : Float;
                  if (GetGameObjectCountChildByHandle(goHnd)=0) and (_misc_GetPortResourcePoint(goHnd, tmpx, tmpz, True)) then
                  begin
                     var i : Integer;
                     for i:=0 to 3 do
                     begin
                        var dirx : Float = 3;
                        var diry, dirz : Float;
                        VectorRotateY(dirx, diry, dirz, i*90);
                        var miscplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_misc);
                        var buiHnd : Integer = CreatePlayerGameObjectHandleByHandle(miscplhnd, gc_racename_misc, gc_basename_bui, tmpx+dirx, 0, tmpz+dirz);
                        SetGameObjectPositionByHandle(buiHnd, tmpx+dirx, 0, tmpz+dirz);
                        GameObjectParentToChildAbsolute(buiHnd, goHnd);
                     end;
                  end;
               end;
            end;
         end;
         if (bavailable) then
         blinkMode := gc_gui_blinkmode_canbuild
         else
         blinkMode := gc_gui_blinkmode_cannotbuild;
         _unit_DoConstructionBlinkGameObject(goHnd, blinkMode, False, True);
      end;
   end;
end;

// _misc_GUIVisualizeLogMessages
//
procedure _misc_GUIVisualizeLogMessages();
begin
   const cElmName = gc_gui_element_logmessage;
   const cMessageFadeInTime = 0.75;
   const cMessageFadeOutTime = 0.75;
   const cMessageShowTime = 8;
   const cFont = gc_font_serif_13;
   var count : Integer = gLogMessages.count;
   if (count>0) then
   begin
      var dpiscale : Float = _gui_GetAbsScaleByDPI(0, 0);
      var elmParent : Integer = _gui_CreateParent(cElmName, 0, gc_halParentLeft, gc_valParentBottom, 4, -round(240*dpiscale), 1000, GetGUIElementFontHeight(gUIConst.font[cFont])*3, False);
      _gui_AbsScaleByDPI(elmParent);
      SetGUIElementVisible(elmParent, (gInterface.gamemode<>gc_gamemode_mainmenu));
      var mincount : Integer = count-gc_MaxLogMessageCount;
      if (mincount<0) then
      mincount := 0;

      var arrComp : array [0..gc_MaxLogMessageCount-1] of String;
      var i : Integer;
      for i:=0 to gc_MaxLogMessageCount-1 do
      arrComp[i] := IntToStr(gLogMessages.msg[i].id)+'|'+gLogMessages.msg[i].s1+'|'+gLogMessages.msg[i].s2;
      for i:=count-1 downto mincount do
      begin
         var enum : Integer = (gLogMessages.count-i);
         var id : Integer = gLogMessages.msg[i].id;
         var time : Float = gLogMessages.msg[i].time;

         // вывод типового сообщения игроку
         const cStr1 = '%s1%';
         const cStr2 = '%s2%';
         var text : String = gConst.logmessage[id];
         _misc_StringToText(text, text);
         if (StrExists(text, cStr1)) then
         text := StrReplace(text, cStr1, gLogMessages.msg[i].s1)
         else
         if (gLogMessages.msg[i].s1<>'') then ErrorLog('_misc_ShowLogMessage, got s1 argument, that doesnt exists in text');
         if (StrExists(text, cStr2)) then
         begin
            if (id<>gc_logmessagesid_unitinfo) then
            text := StrReplace(text, cStr2, gLogMessages.msg[i].s2)
            else
            text := StrReplace(text, cStr2, '')
         end
         else
         if (gLogMessages.msg[i].s2<>'') then ErrorLog('_misc_ShowLogMessage, got s2 argument, that doesnt exists in text');

         var delta : Float = (GetCurrentTime-time);
         var bWarning : Boolean;
         case id of
            gc_logmessagesid_upgradedone, gc_logmessagesid_buildingbuilt, gc_logmessagesid_custom, gc_logmessagesid_unitinfo : bWarning := False;
            else
            bWarning := True;
         end;
         if (id>gc_logmessagesid_none) and (id<>gc_logmessagesid_unitinfo) then
         begin
            if (not gLogMessages.msg[i].ballowdublicates) then
            begin
               var newComp : String = IntToStr(gLogMessages.msg[i].id)+'|'+gLogMessages.msg[i].s1+'|'+gLogMessages.msg[i].s2;
               var j : Integer;
               for j:=count-1 downto 0 do
               if (j<>i) and (arrComp[j]=newComp) then
               gLogMessages.msg[j].time := GetCurrentTime;
            end;
         end;
         var color, shcolor : TColor;
         if (bWarning) then
         begin
            var color1, color2 : TColor;
            _gui_ConvertHexToTColor(gc_font_hex_color_white, color1);
            _gui_ConvertHexToTColor(gc_font_hex_color_red, color2);
            var koef : Float = Sin(GetCurrentTime*7);
            koef := Lerp(0, 1, (koef+1)/2);
            color.r := Lerp(color1.r, color2.r, koef);
            color.g := Lerp(color1.g, color2.g, koef);
            color.b := Lerp(color1.b, color2.b, koef);
            color.a := 1;
            _gui_ConvertHexToTColor(gc_font_hex_color_black, shcolor);
            shcolor.a := 0.5;
         end
         else
         begin
            _gui_ConvertHexToTColor(gc_font_hex_color_white, color);
            _gui_ConvertHexToTColor(gc_font_hex_color_black, shcolor);
         end;
         var elmHnd : Integer = _gui_CreateTextShadowed('logmessage_'+IntToStr(i), elmParent, text, gc_halParentLeft, gc_valParentTop, 0, -enum*GetGUIElementFontHeight(gUIConst.font[cFont]), GetGUIElementWidth(elmParent), GetGUIElementFontHeight(gUIConst.font[cFont]), gc_halLeft, gc_valTop, cFont, color, 2, 2, shcolor, True);
         var elmShHnd : Integer = GetGUIElementIndexByNameParent('logmessage_'+IntToStr(i)+'_shadow', elmParent);
         SetGUIElementTag(elmHnd, id);
         var alpha : Float;
         if (delta<cMessageFadeInTime) then
         begin
            alpha := delta/cMessageFadeInTime;
            if (alpha<=0) then // if you add log messages from gui DoProgress then delta=0
            alpha := 0.001;
            SetGUIElementUserBlend(elmHnd, alpha);
            SetGUIElementUserBlend(elmShHnd, alpha);
         end
         else
         begin
            alpha := cMessageShowTime/(delta+gc_epsilon);
            alpha := Lerp(-cMessageShowTime, 1, alpha);
            alpha := Clamp(alpha, 0, 1);
            SetGUIElementUserBlend(elmHnd, alpha);
            SetGUIElementUserBlend(elmShHnd, alpha);
         end;
         SetGUIElementVisible(elmHnd, (alpha>0));
         SetGUIElementVisible(elmShHnd, (alpha>0));
      end;
      for i:=count-1 downto 0 do
      begin
         var elmHnd : Integer = GetGUIElementIndexByNameParent('logmessage_'+IntToStr(i), elmParent);
         var elmShHnd : Integer = GetGUIElementIndexByNameParent('logmessage_'+IntToStr(i)+'_shadow', elmParent);
         if (elmHnd<>0) and (not GetGUIElementVisible(elmHnd)) then
         begin
            gLogMessages.Clear(i);
            RemoveGUIElement(elmHnd);
         end
         else
         if (i<mincount) then
         gLogMessages.Clear(i);
         if (elmShHnd<>0) and (not GetGUIElementVisible(elmShHnd)) then
         RemoveGUIElement(elmShHnd);
      end;
      gLogMessages.Sort;
      gLogMessages.CalcCount;
   end;
end;

// _misc_GUIVisualizeChatMessages
//
procedure _misc_GUIVisualizeChatMessages();
begin
   const cElmName = gc_gui_element_chatmessage;
   const cMessageFadeInTime = 0;
   const cMessageFadeOutTime = 0.75;
   const cMessageShowTime = 16;
   const cFont = gc_font_serif_13;
   var count : Integer = gChatMessages.count;
   if (count>0) then
   begin
      //var bottomOffset : Integer = 164+32+32+30;
      //var cMessageShowMaxAtOnce : Integer = (GetViewerHeight-topOffset-240-(gc_MaxLogMessageCount+1)*GetGUIElementFontHeight(gUIConst.font[gc_font_serif_13])) div GetGUIElementFontHeight(gUIConst.font[cFont]);
      var dpiscale : Float = _gui_GetAbsScaleByDPI(0, 0);
      var fontheight : Integer = round(GetGUIElementFontHeight(gUIConst.font[gc_font_serif_13])*dpiscale);
      var bottomOffset : Integer = -20-240-round((gc_MaxLogMessageCount+1)*fontheight);
      var cMessageShowMaxAtOnce : Integer = Max(1, round((GetViewerHeight-(164+32)+bottomOffset)/fontheight));

      var elmParent : Integer = _gui_CreateParent(cElmName, 0, gc_halParentLeft, gc_valParentBottom, 4, bottomOffset-cMessageShowMaxAtOnce*fontheight, 1000, 0, False);
      _gui_AbsScaleByDPI(elmParent);
      //var elmParent : Integer = _gui_CreateParent(cElmName, 0, gc_halParentLeft, gc_valParentTop, 4, topOffset, 1000, GetGUIElementFontHeight(gUIConst.font[cFont])*3, False);
      SetGUIElementVisible(elmParent, (gInterface.gamemode<>gc_gamemode_mainmenu));
      var i : Integer;
      var mincount : Integer = count-cMessageShowMaxAtOnce;
      if (mincount<0) then
      mincount := 0;

      var bchatvisible : Boolean;
      var elmchat : Integer = GetGUIElementIndexByNameParent('ChatConsole', _gui_GetTop);
      if (elmchat<>0) then
      bchatvisible := GetGUIElementVisible(elmchat);

      var enum : Integer;
      //for i:=count-1 downto mincount do
      for i:=mincount to count-1 do
      begin
         var time : Float = gChatMessages.msg[i].time;
         if (time<>0) then
         begin
            // вывод типового сообщения игроку
            var text : String;
            var mode : Integer = gChatMessages.msg[i].mode;
            case mode of
               gc_chatmessage_mode_all : begin
                  text := gc_font_color_orange+GetLocaleTableListItemByID(gc_loc_gui, 'chat.all');
                  text := StrReplace(text, '%from%', gChatMessages.msg[i].sfrom);
                  text := StrReplace(text, '%text%', gc_font_color_white+gChatMessages.msg[i].text);
               end;
               gc_chatmessage_mode_private : begin
                  text := gc_font_color_orange+GetLocaleTableListItemByID(gc_loc_gui, 'chat.private');
                  text := StrReplace(text, '%from%', gChatMessages.msg[i].sfrom);
                  text := StrReplace(text, '%to%', gChatMessages.msg[i].sto);
                  text := StrReplace(text, '%text%', gc_font_color_white+gChatMessages.msg[i].text);
               end;
               gc_chatmessage_mode_ally : begin
                  text := gc_font_color_green+GetLocaleTableListItemByID(gc_loc_gui, 'chat.ally');
                  text := StrReplace(text, '%from%', gChatMessages.msg[i].sfrom);
                  text := StrReplace(text, '%text%', gc_font_color_white+gChatMessages.msg[i].text);
               end;
            end;
            var color, shcolor : TColor;
            _gui_ConvertHexToTColor(gc_font_hex_color_white, color);
            _gui_ConvertHexToTColor(gc_font_hex_color_black, shcolor);
            var elmHnd : Integer = _gui_CreateTextShadowed('chatmessage_'+IntToStr(i), elmParent, text, gc_halParentLeft, gc_valParentTop, 0, {-(gChatMessages.count-i)}enum*GetGUIElementFontHeight(gUIConst.font[cFont]), GetGUIElementWidth(elmParent), GetGUIElementFontHeight(gUIConst.font[cFont]), gc_halLeft, gc_valTop, cFont, color, 2, 2, shcolor, True);
            var elmShHnd : Integer = GetGUIElementIndexByNameParent('chatmessage_'+IntToStr(i)+'_shadow', elmParent);
            var alpha, delta : Float;
            if (bchatvisible) then
            delta := cMessageFadeInTime
            else
            delta := (GetCurrentTime-time);

            if delta=0 then
            alpha := 1
            else
            alpha := cMessageShowTime/(delta+gc_epsilon);
            alpha := Lerp(-cMessageShowTime, 1, alpha);
            alpha := Clamp(alpha, 0, 1);
            SetGUIElementUserBlend(elmHnd, alpha);
            SetGUIElementUserBlend(elmShHnd, alpha);

            SetGUIElementVisible(elmHnd, (alpha>0));
            SetGUIElementVisible(elmShHnd, (alpha>0));
            enum := enum+1;
         end;
      end;
      gChatMessages.Sort;
      gChatMessages.CalcCount;
   end;
end;

// _misc_UpdateTopology
//
procedure _misc_UpdateTopology(minX, minY, maxX, maxY : Float);
begin
   procedure CheckZoneBuffers(minI, minJ, maxI, maxJ: Integer);
   begin
      var i, j : Integer;
      gIntegerList.Clear;
      for i := minI to maxI do
      for j := minJ to maxJ do
      begin
         var zone : Integer = TopologyGetZoneIndexByArrayIndices(i, j);
         if zone >= 0 then
         begin
            var pBuffer : Pointer = TopologyGetZoneBufferByIndex(zone);
            var k : Integer;
            var dan : Float;
            for k := 0 to gc_MaxPlayerCount-1 do
            dan := dan + TTopZone(pBuffer).danger[k];

            if dan <> 0 then
            begin
               if gIntegerList.IndexOf(zone) < 0 then
               gIntegerList.Add(zone);
            end;
         end;
      end;
   end;

   procedure CheckZone(zone : Integer; var minI, minJ, maxI, maxJ : Integer);
   begin
      var zMinI, zMinJ, zMaxI, zMaxJ : Integer;
      TopologyGetZoneExtents(zone, zMinI, zMinJ, zMaxI, zMaxJ);
      if zMinI < minI then minI := zMinI;
      if zMaxI > maxI then maxI := zMaxI;
      if zMinJ < minJ then minJ := zMinJ;
      if zMaxJ > maxJ then maxJ := zMaxJ;
   end;

   var zone, i, j, k : Integer;
   var mw : Integer = GetMapWidth;
   var mh : Integer = GetMapHeight;

   var minI : Integer = Max(Floor(2 * minX + mw), 0);
   var maxI : Integer = Min(Floor(2 * maxX + mw), 2*mw-1);
   var minJ : Integer = Max(Floor(2 * minY + mh), 0);
   var maxJ : Integer = Min(Floor(2 * maxY + mh), 2*mh-1);

   var zCount : Integer = TopologyGetZonesCount;
   for zone := 0 to zCount-1 do
   TopologySetZoneCheckedByIndex(zone, false);

   for i := minI to maxI do
   for j := minJ to maxJ do
   begin
      var zone : Integer = TopologyGetZoneIndexByArrayIndices(i, j);
      TopologySetZoneCheckedByIndex(zone, true);
   end;

   var chMinI : Integer = gc_MaxInt;
   var chMaxI : Integer = -gc_MaxInt;
   var chMinJ : Integer = gc_MaxInt;
   var chMaxJ : Integer = -gc_MaxInt;

   for zone := 0 to zCount-1 do
   if TopologyGetZoneCheckedByIndex(zone) then
   begin
      CheckZone(zone, chMinI, chMinJ, chMaxI, chMaxJ);
      var cCount : Integer = TopologyGetZoneConnectionsCountByIndex(zone);
      for i := 0 to cCount-1 do
      begin
         var con : Integer = TopologyGetZoneConnectionByIndex(zone, i);
         CheckZone(con, chMinI, chMinJ, chMaxI, chMaxJ);
      end;
   end;

   // delete units from zones data
   var xMin, xMax, yMin, yMax : Float;
   _misc_ColGridIndicesToPos(chMinI, chMinJ, xMin, yMin);
   _misc_ColGridIndicesToPos(chMaxI, chMaxJ, xMax, yMax);

   var gridMinX, gridMaxX : Integer;
   var gridMinY, gridMaxY : Integer;
   _misc_PosToScanGridIndices(xMin, yMin, gridMinX, gridMinY);
   _misc_PosToScanGridIndices(xMax, yMax, gridMaxX, gridMaxY);

   for i := gridMinX to gridMaxX do
   for j := gridMinY to gridMaxY do
   begin
      if (i>=0) and (i<gc_scangrid_countx) and (j>=0) and (j<gc_scangrid_county) then
      begin
         for k := gScanGrid[i, j].GetCount-1 downto 0 do
         begin
            var goHnd : Integer = gScanGrid[i, j].Get(k);
            _unit_TopologyRemove(goHnd);
         end;
      end
      else
      ErrorLog('_misc_UpdateTopology out of range i='+IntToStr(i)+' j='+IntToStr(j)+'chMinI='+IntToStr(chMinI)+' chMinJ='+IntToStr(chMinJ));
   end;

   // update zones buffer info
   for zone := 0 to zCount-1 do
   if TopologyGetZoneCheckedByIndex(zone) then
   begin
      _misc_UpdateTopZoneInfo(zone);
      var cCount : Integer = TopologyGetZoneConnectionsCountByIndex(zone);
      for i := 0 to cCount-1 do
      begin
         var con : Integer = TopologyGetZoneConnectionByIndex(zone, i);
         _misc_UpdateTopZoneInfo(con);
      end;
   end;

   // check if all buffer data was deleted
   CheckZoneBuffers(minI, minJ, maxI, maxJ);

   // still have zones with non-cleared buffer
   if gIntegerList.GetCount > 0 then
   begin
      var badCount : Integer = gIntegerList.GetCount;
      for i := 0 to zCount-1 do
      TopologySetZoneCheckedByIndex(i, false);

      for i := 0 to badCount-1 do
      begin
         var zone : Integer = gIntegerList.Get(i);
         TopologySetZoneCheckedByIndex(zone, true);
         var cCount : Integer = TopologyGetZoneConnectionsCountByIndex(zone);
         for j := 0 to cCount-1 do
         begin
            var con : Integer = TopologyGetZoneConnectionByIndex(zone, j);
            TopologySetZoneCheckedByIndex(con, true);
         end;
      end;

      for i := 0 to gc_MaxPlayerCount-1 do
      begin
         var plHnd : Integer = GetPlayerHandleByIndex(i);
         for j := GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
         begin
            var goHnd : Integer = GetGameObjectHandleByIndex(j, plHnd);
            var pObj : Pointer = _unit_GetTObj(goHnd);
            var zone : Integer = TObj(pObj).topZone;
            if (zone >= 0) and TopologyGetZoneCheckedByIndex(zone) then
            _unit_TopologyRemove(goHnd);
         end;
      end;

      for i := 0 to badCount-1 do
      begin
         var zone : Integer = gIntegerList.Get(i);
         _misc_UpdateTopZoneInfo(zone);
         var cCount : Integer = TopologyGetZoneConnectionsCountByIndex(zone);
         for j := 0 to cCount-1 do
         begin
            var con : Integer = TopologyGetZoneConnectionByIndex(zone, j);
            _misc_UpdateTopZoneInfo(con);
         end;
      end;
   end;

   _misc_BaseTopologyUpdate(minX, minY, maxX, maxY);
end;

// _misc_UpdateWallTopology
//
procedure _misc_UpdateWallTopology(var pWallCluster : Pointer);
begin
   var minx, minz : Float = 9999999;
   var maxx, maxz : Float = -9999999;
   var i : Integer;
   for i:=0 to TWallCluster(pWallCluster).Cells.GetCount-1 do
   begin
      var p : Pointer = TWallCluster(pWallCluster).Cells.GetPointer(i);
      var x : Float = TWallCell(p).x;
      var z : Float = TWallCell(p).y;
      if minx>x then minx := x;
      if minz>z then minz := z;
      if maxx<x then maxx := x;
      if maxz<z then maxz := z;
   end;
   minx := minx-3;
   minz := minz-3;
   maxx := maxx+3;
   maxz := maxz+3;
   _misc_UpdateTopology(minx, minz, maxx, maxz);
end;

// _misc_UpdateTopologyZoneSizeToMapSize
//
procedure _misc_UpdateTopologyZoneSizeToMapSize(bUpdateTopology : Boolean);
begin
   const cDefaultMapSize = 320;
   const cDefaultZoneSize = 15;
   const cDefaulCountFloat = cDefaultMapSize/cDefaultZoneSize;
   var zonesize : Integer = Round(Sqrt((GetMapWidth*GetMapHeight)/(cDefaulCountFloat*cDefaulCountFloat)));
   if (zonesize<>TopologyGetZoneSize) or (zonesize<>TopologyGetMaxZoneSize) then
   begin
      TopologySetZoneSize(zonesize);
      TopologySetMaxZoneSize(zonesize);
   end;
   if (bUpdateTopology) then
   begin
      TopologyBuild;
      var minx : Float = -GetMapWidth div 2;
      var minz : Float = -GetMapHeight div 2;
      var maxx : Float = GetMapWidth div 2;
      var maxz : Float = GetMapHeight div 2;
      _misc_UpdateTopology(minx, minz, maxx, maxz);
   end;
end;

// _misc_OpenCloseGates
//
procedure _misc_OpenCloseGates(const goHnd : integer; const bOpen, topUpdate : Boolean);
begin
   var mask14o : array[0..1] of string;
   mask14o[0] := '200002';
   mask14o[1] := '200002';
   var mask14c : array[0..1] of string;
   mask14c[0] := '211112';
   mask14c[1] := '211112';
   var mask15o : array[0..5] of string;
   mask15o[0] := '22';
   mask15o[1] := '00';
   mask15o[2] := '00';
   mask15o[3] := '00';
   mask15o[4] := '00';
   mask15o[5] := '22';
   var mask15c : array[0..5] of string;
   mask15c[0] := '22';
   mask15c[1] := '11';
   mask15c[2] := '11';
   mask15c[3] := '11';
   mask15c[4] := '11';
   mask15c[5] := '22';

   var mask16o : array[0..7] of string;
   mask16o[0] := '00000000';
   mask16o[1] := '00000000';
   mask16o[2] := '00000000';
   mask16o[3] := '00000000';
   mask16o[4] := '00000000';
   mask16o[5] := '00000000';
   mask16o[6] := '00000000';
   mask16o[7] := '00000000';
   var mask16c : array[0..7] of string;
   mask16c[0] := '00000100';
   mask16c[1] := '00000110';
   mask16c[2] := '00001111';
   mask16c[3] := '00011110';
   mask16c[4] := '01111000';
   mask16c[5] := '11110000';
   mask16c[6] := '01110000';
   mask16c[7] := '00100000';

   var mask17o : array[0..5] of string;
   mask17o[0] := '000000';
   mask17o[1] := '000000';
   mask17o[2] := '000000';
   mask17o[3] := '000000';
   mask17o[4] := '000000';
   mask17o[5] := '000000';
   var mask17c : array[0..5] of string;
   mask17c[0] := '000000';
   mask17c[1] := '011000';
   mask17c[2] := '011100';
   mask17c[3] := '001110';
   mask17c[4] := '000111';
   mask17c[5] := '000010';
   function IsCollision(const gate : Integer; const bOpen : Boolean; const i, j : Integer) : Integer;
   begin
      var c : string;
      if (bOpen) then
      begin
         case gate of
            14 : c := SubStr(mask14o[i], j+1, 1);
            15 : c := SubStr(mask15o[i], j+1, 1);
            16 : c := SubStr(mask16o[i], j+1, 1);
            17 : c := SubStr(mask17o[i], j+1, 1);
         end;
      end
      else
      begin
         case gate of
            14 : c := SubStr(mask14c[i], j+1, 1);
            15 : c := SubStr(mask15c[i], j+1, 1);
            16 : c := SubStr(mask16c[i], j+1, 1);
            17 : c := SubStr(mask17c[i], j+1, 1);
         end;
      end;
      Result := StrToInt(c);
   end;
   procedure GetNumColsRows(const gate : Integer; var cols, rows : Integer; var shiftx, shiftz : Float);
   begin
      case gate of
         14 : begin
            cols := 6;
            rows := 2;
            shiftz := -0.5;
            shiftx := -1.5;
         end;
         15 : begin
            cols := 2;
            rows := 6;
            shiftx := -0.5;
            shiftz := -1.5;
         end;
         16 : begin
            cols := 8;
            rows := 8;
            shiftz := -2;
            shiftx := -2;
         end;
         17 : begin
            cols := 6;
            rows := 6;
            shiftz := -1.5;
            shiftx := -1.5;
         end;
      end;
   end;
   var i, j, cols, rows : Integer;
   var shiftx, shiftz : Float;
   var posx : Float = GetGameObjectPositionXByHandle(goHnd);
   var posz : Float = GetGameObjectPositionZByHandle(goHnd);
   var s : string = GetGameObjectBaseNameByHandle(goHnd);
   s := SubStr(s, StrLength(s)-1, 2);
   var gate : Integer = strtoint(s);
   GetNumColsRows(gate, cols, rows, shiftx, shiftz);

   for i := 0 to rows-1 do
   for j := 0 to cols-1 do
   begin
      var x : Float = j/2+shiftx+posx;
      var z : Float = i/2+shiftz+posz;
      var tag : Integer = IsCollision(gate, bOpen, i, j);
      if (tag=1) then
      MapDrawCollision(x, z, gc_collisiontag_wall, 0.5, false)
      else
      if (tag=0) then
      MapDrawCollision(x, z, 0, 0.5, false);
   end;
   var minx, maxx : Float = posx;
   var minz, maxz : Float = posz;

   minx := minx-3;
   minz := minz-3;
   maxx := maxx+3;
   maxz := maxz+3;

   if topUpdate then
   _misc_UpdateTopology(minx, minz, maxx, maxz);
   //log(floattostr(minx)+' '+floattostr(minz)+' '+floattostr(maxx)+' 'floattostr(maxz));
end;

// _misc_AddUpdateArea
//
procedure _misc_AddUpdateArea(minX, minY, maxX, maxY : Float);
begin
   var unite : Boolean;
   var rect : TRect;
   rect.minX := minX;
   rect.minY := minY;
   rect.maxX := maxX;
   rect.maxY := maxY;

   var i, j, uInd : Integer;
   for uInd := 0 to gInt_UpdateAreasCount-1 do
   if _misc_IntersectRects(rect, gUpdateAreas[uInd]) then
   begin
      gUpdateAreas[uInd].minX := MinFloat(minX, gUpdateAreas[uInd].minX);
      gUpdateAreas[uInd].minY := MinFloat(minY, gUpdateAreas[uInd].minY);
      gUpdateAreas[uInd].maxX := MaxFloat(maxX, gUpdateAreas[uInd].maxX);
      gUpdateAreas[uInd].maxY := MaxFloat(maxY, gUpdateAreas[uInd].maxY);
      unite := true;
      break;
   end;

   if unite then
   begin
      while unite do
      begin
         unite := false;
         for i := 0 to gInt_UpdateAreasCount-1 do
         if (i <> uInd) and _misc_IntersectRects(gUpdateAreas[uInd], gUpdateAreas[i]) then
         begin
            var minInd : Integer = Min(i, uInd);
            var maxInd : Integer = Max(i, uInd);

            gUpdateAreas[minInd].minX := MinFloat(gUpdateAreas[maxInd].minX, gUpdateAreas[minInd].minX);
            gUpdateAreas[minInd].minY := MinFloat(gUpdateAreas[maxInd].minY, gUpdateAreas[minInd].minY);
            gUpdateAreas[minInd].maxX := MaxFloat(gUpdateAreas[maxInd].maxX, gUpdateAreas[minInd].maxX);
            gUpdateAreas[minInd].maxY := MaxFloat(gUpdateAreas[maxInd].maxY, gUpdateAreas[minInd].maxY);

            for j := maxInd to gInt_UpdateAreasCount-2 do
            gUpdateAreas[j] := gUpdateAreas[j+1];

            gInt_UpdateAreasCount := gInt_UpdateAreasCount-1;
            unite := true;
            uInd := minInd;
            break;
         end;
      end;
   end
   else
   begin
      if gInt_UpdateAreasCount < gc_top_MaxUpdateAreas-1 then
      gInt_UpdateAreasCount := gInt_UpdateAreasCount + 1
      else
      _misc_UpdateTopology(gUpdateAreas[gInt_UpdateAreasCount-1].minX, gUpdateAreas[gInt_UpdateAreasCount-1].minY,
      gUpdateAreas[gInt_UpdateAreasCount-1].maxX, gUpdateAreas[gInt_UpdateAreasCount-1].maxY);

      var ind : Integer = gInt_UpdateAreasCount-1;

      gUpdateAreas[ind].minX := minX;
      gUpdateAreas[ind].minY := minY;
      gUpdateAreas[ind].maxX := maxX;
      gUpdateAreas[ind].maxY := maxY;
   end;
end;

// _misc_UnitTopologyUpdate
//
procedure _misc_UnitTopologyUpdate(goHnd : Integer);
begin
   const cTopologySize = 6;
   var minX : Float = GetGameObjectPositionXByHandle(goHnd)-cTopologySize;
   var minY : Float = GetGameObjectPositionZByHandle(goHnd)-cTopologySize;
   var maxX : Float = GetGameObjectPositionXByHandle(goHnd)+cTopologySize;
   var maxY : Float = GetGameObjectPositionZByHandle(goHnd)+cTopologySize;
   var pObjProp : Pointer = _unit_GetObjProp(goHnd);
   if (pObjProp <> nil) and TObjProp(pObjProp).bWall then
   _misc_AddUpdateArea(minX, minY, maxX, maxY)
   else
   _misc_BaseTopologyUpdate(minX, minY, maxX, maxY);
end;

// _misc_RegisterPlayerProgressSectionOnReachedFunction
//
procedure _misc_RegisterPlayerProgressSectionOnReachedFunction(bClear : Boolean);
begin
   var i, j : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      var plHnd : Integer = GetPlayerHandleByIndex(i);
      for j:=0 to GetPlayerProgressSectionCount(plHnd)-1 do
      begin
         if (bClear) then
         SetPlayerProgressSectionOnReachedFuncByIndex(plHnd, j, '')
         else
         SetPlayerProgressSectionOnReachedFuncByIndex(plHnd, j, '_misc_OnPlayerProgressSectionReached');
      end;
   end;
end;

// _misc_OnPlayerProgressSectionReached
//
procedure _misc_OnPlayerProgressSectionReached(plHnd, interval : Integer);
begin
   var plind : Integer = GetPlayerIndexByHandle(plHnd);
   var rinterval : Integer = Integer(AddrToPointer(interval));
end;

// _misc_ResetProgressPlayer
//
procedure _misc_ResetProgressPlayer;
begin
   gProgress.lastprogresshistorytime := random;
   gProgress.lastmiscplsecmaxtime := random*gc_progress_TimeMiscPlSecMax;
   gProgress.lastpoolplsecmaxtime := random*gc_progress_TimePoolPlSecMax;
   gProgress.lastsearchenemycountmidtime := random*gc_progress_TimeSearchEnemyCountMid;
   gProgress.lastsearchenemycountsumtime := random;
   gProgress.lastsearchenemycountertime := random*gc_progress_TimeSearchEnemyCounter;
   gProgress.lastprogressstatisticstime := random*gc_progress_TimeProgressStatistics;
   gProgress.lastprogresstopzonestime := random*gc_progress_TimeProgressTopZones;
   gProgress.lastupdateenemyticktime := 0;
   gProgress.lastupdateenemytickteam := 0;
   var i : Integer;
   for i:=0 to 5 do
   begin
      gProgress.lastsoundprogressfreqtime[i] := random*gc_progress_TimeSoundProgressFreq;
      gProgress.lastsoundprogresscounterfreqtime[i] := random*gc_progress_TimeSoundProgressCounterFreq;
   end;
end;

// _misc_ResetGameData
//
procedure _misc_ResetGameData();
begin
   _player_InitAllPlayers;
   _res_InitEconomy;
   _res_ResGridClear;
   _unit_ScanGridClear;
   _unit_IdleGridClear;
   _misc_ResetProgressPlayer;
   gGOPathList.Clear;
   gWaterPathList.Clear;
   gEnemyInfoList.Clear;
   var i, j : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   for j:=0 to 9 do
   gCtrlGroups[i][j].Clear;
   gSelectedObjects.Clear;
   gSelectedSquads.Clear;
   gLogMessages.ClearAll;
   gChatMessages.ClearAll;
   gWallSystem.Clear;
   gWallClusterCreate.Clear(False);
   gWallCluster.Clear(True);
   gWriteNewRequestList.Clear;

   gfloat_gui_musiclastbattletimestart := 0;
   gfloat_gui_musiclastbattletimeend := 0;
   gfloat_gui_musiclastnationaltimestart := 0;
   gfloat_gui_musiclastnationaltimeend := 0;

   var p : Pointer = gSoundGrid;
   if (p<>nil) then
   FillMem(p, SizeOf(gSoundGrid), 0);
   p := gArcherGrid;
   if (p<>nil) then
   FillMem(p, SizeOf(gArcherGrid), 0);
   p := gSoundManager;
   if (p<>nil) then
   FillMem(p, SizeOf(gSoundManager), 0);
   p := gProjSimulatePoints;
   if (p<>nil) then
   FillMem(p, SizeOf(gProjSimulatePoints), 0);
   p := gCanPlaceBuildingWalls;
   if (p<>nil) then
   FillMem(p, SizeOf(gCanPlaceBuildingWalls), 0);

   var tmpinterface : TInterface;
   gInterface := tmpinterface;
   if (gScenario.bexists) then begin
      var clearscenario : TScenario;
      clearscenario.Create('');
      clearscenario.bexists := False;
      clearscenario.bactive := False;
      gScenario:=clearscenario;
      gScenarioTmp:=clearscenario;
   end;
end;

// _misc_FillChatList
//
procedure _misc_FillChatList(bAddLastOnly : Boolean);
begin
   var elmParent : Integer;
   if (gInternetShell.bshowroom) then
   begin
      if gInternetShell.bhistoricalbattle then
      elmParent := GetGUIElementIndexByNameParent('HistoricalBattle', _gui_GetTop)
      else
      elmParent := GetGUIElementIndexByNameParent('CustomGame', _gui_GetTop);
   end
   else
   elmParent := GetGUIElementIndexByNameParent('InternetShell', _gui_GetTop);
   if (elmParent<>0) then
   begin
      var elmChatPlace : Integer = GetGUIElementIndexByNameParent('chatplace', elmParent);
      var elmChat : Integer = GetGUIElementIndexByNameParent('chatback', elmChatPlace);
      var elmChatList : Integer = GetGUIElementIndexByNameParent('chat', elmChat);
      var topIndex : Integer = GetGUIListBoxTopIndex(elmChatList);
      var itemIndex : Integer = GetGUIListBoxItemIndex(elmChatList);
      var bScrollToEnd : Boolean = (topIndex=(GetGUIListBoxItemsCount(elmChatList)-gc_gui_chat_linescount));
      if (not bAddLastOnly) then
      GUIListBoxClearSilent(elmChatList);

      var lastind : Integer;
      if (gInternetShell.bshowroom) then
      lastind := gInternetShell.roomchatind-1
      else
      lastind := gInternetShell.chatind-1;

      var srcmsgprev : String;
      var i : Integer;
      for i:=0 to lastind do
      begin
         var srcmsg, msg : String;
         if (gInternetShell.bshowroom) then
         begin
            if (bAddLastOnly) then
            i := gInternetShell.roomchatind-1;
            if i>0 then
            srcmsgprev := gInternetShell.roomchat[i-1];
            srcmsg := gInternetShell.roomchat[i];
         end
         else
         begin
            if (bAddLastOnly) then
            i := gInternetShell.chatind-1;
            if i>0 then
            srcmsgprev := gInternetShell.chat[i-1];
            srcmsg := gInternetShell.chat[i];
         end;
         var nickFrom : String;
         var nickTo : String;
         if (GetDelimiterStringCount(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar)>=6) then
         begin
            var idFrom : String = GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 0);
            var idTo : String = GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 1);
            if (idFrom<>IntToStr(LanMyInfoID)) and (srcmsgprev=srcmsg) then
            continue;
            if (idTo<>idFrom) then
            begin
               nickFrom := GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 2);
               nickTo := GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 3);
               var lang : String = GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 4);
               msg := gc_font_color_white+nickFrom+gc_font_color_default;
               if (idTo<>'0') then
               msg := msg+'-->'+gc_font_color_white+nickTo+gc_font_color_default;
               if (msg<>'') then
               msg := msg+gc_font_color_white+': '+gc_font_color_default;
            end;
            msg := msg+GetDelimiterStringByIndex(srcmsg, gc_gui_delimiterchar, gc_gui_quotechar, 5)
         end
         else
         msg := srcmsg;

         var wrapMsg : String = GetGUIWrappedTextFormatByFont(GetGUIElementFont(elmChatList), msg, GetGUIElementWidth(elmChatList)-8);
         var l : Integer;
         repeat
            var lprev : Integer = l+1;
            var sl : String;
            l := StrPosEx(gc_gui_breakline, wrapMsg, l+1);
            if (l=0) then
            sl := SubStr(wrapMsg, lprev, StrLength(wrapMsg))
            else
            sl := SubStr(wrapMsg, lprev, l-lprev);
            if (not gbool_internetshell_friendschannel) then
            GUIListBoxAddItem(elmChatList, sl, i)
            else
            begin
               if (((TInternetShellClient(gInternetShell.clients.GetByLanID(LanMyInfoID)).nick=nickFrom) and (nickTo<>'')) or (TInternetShellClient(gInternetShell.clients.GetByLanID(LanMyInfoID)).nick=nickTo)) then
               GUIListBoxAddItem(elmChatList, sl, i);
            end;

            {if (roomchat) then
            begin
               if (gInetShell.chatprivatemsg=0) or (idTo=LanMyInfoID) or (idFrom=LanMyInfoID) then
               GUIListBoxAddItem(elmHandle, sl, idFrom);
            end
            else
            begin
               if (gInetShell.chatprivatemsg=0) or (idto=LanMyInfoID) or (idfrom=LanMyInfoID) then
               GUIListBoxAddItem(elmHandle, sl, idFrom);
            end;}
         until l<=0;

         //GUIListBoxAddItem(elmChatList, msg, i);

         if (bAddLastOnly) then
         break;
      end;

      if (bScrollToEnd) then
      SetGUIListBoxTopIndex(elmChatList, GetGUIListBoxItemsCount(elmChatList)-1)
      else
      SetGUIListBoxTopIndex(elmChatList, topIndex);
      SetGUIListBoxItemIndex(elmChatList, itemIndex);

      var bNeedScroll : Boolean = _gui_SetListBoxHeight(elmChatList, gc_gui_chat_linescount);
      _gui_AddScroller(elmChatList, gc_halParentRightWidth, gc_valParentTop, 28-4+5, 14+3-2, 0, GetGUIElementHeight(elmChat), bNeedScroll);
      _gui_UpdateListBoxScrollerPos(elmChatList);
   end;
end;

// _misc_LanGetMapNameData
//
procedure _misc_LanGetMapNameData(psession : Pointer; var statetag, humancount, aicount, closecount, ping, rank : Integer);
begin
   if (psession<>nil) then
   begin
      var qpstate : Integer;
      var mapname : String = TInternetShellSession(psession).mapname;
      var dc : Char = gc_gui_delimiterchar;
      var qc : Char = gc_gui_quotechar;
      var delimiterStringCount : Integer = GetDelimiterStringCount(mapname, dc, gc_gui_quotechar);
      statetag := StrToInt(GetDelimiterStringByIndex(mapname, dc, qc, 0));
      if (delimiterStringCount>=2) then
      humancount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 1));
      if (delimiterStringCount>=3) then
      aicount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 2));
      if (delimiterStringCount>=4) then
      closecount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 3));
      if (delimiterStringCount>=5) then
      ping := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 4));
      if (delimiterStringCount>=6) then
      rank := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 5));
      if (delimiterStringCount>=10) then
      qpstate := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 9));

      var maxplayers : Integer = TInternetShellSession(psession).maxplayers;
      var humancount : Integer = TInternetShellSession(psession).clientscount;
      var bfull : Boolean = (maxplayers>0) and ((humancount+aicount+closecount)>=maxplayers);
      var bclosed : Boolean = TInternetShellSession(psession).bclosed;
      var blocked : Boolean = TInternetShellSession(psession).blocked;
      statetag := statetag or (1 shl 0);
      if (bfull) or (qpstate=1) then
      statetag := statetag or (1 shl 1);
      if (blocked) or (qpstate=1) then
      statetag := statetag or (1 shl 2);
   end;
end;

{// _misc_LanApplyMapNameData
//
procedure _misc_LanApplyMapNameData(psession : Pointer; var statetag, humancount, aicount, closecount, ping, rank : Integer);
begin
   if (psession<>nil) then
   begin
      var mapname : String = TInternetShellSession(psession).mapname;
      var dc : Char = gc_gui_delimiterchar;
      var qc : Char = gc_gui_quotechar;
      var delimiterStringCount : Integer = GetDelimiterStringCount(mapname, dc, gc_gui_quotechar);
      statetag := StrToInt(GetDelimiterStringByIndex(mapname, dc, qc, 0));
      if (delimiterStringCount>=2) then
      aicount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 1));
      if (delimiterStringCount>=3) then
      humancount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 2));
      if (delimiterStringCount>=4) then
      closecount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 3));
      if (delimiterStringCount>=5) then
      ping := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 4));
      if (delimiterStringCount>=6) then
      rank := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 5));

      var maxplayers : Integer = TInternetShellSession(psession).maxplayers;
      var humancount : Integer = TInternetShellSession(psession).clientscount;
      var bfull : Boolean = (maxplayers>0) and ((humancount+aicount+closecount)>=maxplayers);
      var bclosed : Boolean = TInternetShellSession(psession).bclosed;
      var blocked : Boolean = TInternetShellSession(psession).blocked;
      statetag := statetag or (1 shl 0);
      if (bfull) then
      statetag := statetag or (1 shl 1);
      if (blocked) then
      statetag := statetag or (1 shl 2);
   end;
end;

// _misc_LanGetMapNameData
//
procedure _misc_LanGetMapNameData_OLD(const masterid : Integer; var statetag, humancount, aicount, closecount, ping, rank : Integer; var pass : String);
begin
   var psessions : Pointer = gInternetShell.sessions.GetByMasterID(masterid);
   if (psessions<>nil) then
   begin
      var mapname : String = TInternetShellSession(psessions).mapname;
      var dc : Char = gc_gui_delimiterchar;
      var qc : Char = gc_gui_quotechar;
      var delimiterStringCount : Integer = GetDelimiterStringCount(mapname, dc, gc_gui_quotechar);
      statetag := StrToInt(GetDelimiterStringByIndex(mapname, dc, qc, 0));
      if (delimiterStringCount>=2) then
      aicount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 1));
      if (delimiterStringCount>=3) then
      humancount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 2));
      if (delimiterStringCount>=4) then
      closecount := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 3));
      if (delimiterStringCount>=5) then
      ping := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 4));
      if (delimiterStringCount>=6) then
      rank := StrToInt(GetDelimiterStringByIndex(mapname, dc, gc_gui_quotechar, 5));

      var maxplayers : Integer = TInternetShellSession(psessions).maxplayers;
      var bfull : Boolean = (maxplayers>0) and ((humancount+aicount+closecount)>=maxplayers);
      var bclosed : Boolean = TInternetShellSession(psessions).bclosed;
      var blocked : Boolean = TInternetShellSession(psessions).blocked;
      statetag := statetag or (1 shl 0);
      if (bfull) then
      statetag := statetag or (1 shl 1);
      if (blocked) then
      statetag := statetag or (1 shl 2);
      if (pass<>'') then
      statetag := statetag or (1 shl 3);
   end;
end;}

// _misc_RemoveInvalidCharsInStringByFilter
//
procedure _misc_RemoveInvalidCharsInStringByFilter(text, filter : String; var output : String);
begin
   output := '';
   var i, j : Integer;
   for i:=1 to StrLength(text) do
   begin
      var bvalid : Boolean;
      var tchar : String = SubStr(text, i, 1);
      for j:=1 to StrLength(filter) do
      begin
         var filterchar : String = SubStr(filter, j, 1);
         if (filterchar=tchar) then
         begin
            bvalid := True;
            break;
         end;
      end;
      if (bvalid) then
      output := output+tchar;
   end;
   if (text<>'') and (output='') then
   output := 'nonvalid';
end;

// _misc_LanCombinePlayerInfo
//
procedure _misc_LanCombinePlayerInfo(var previnfo, newinfo : String);
begin
   var tmpclient : TInternetShellClient;
   tmpclient.info := previnfo;
   tmpclient.ApplyDataFromInfo();

   var totalphys, availphys, totalvirt, availvirt : Integer;
   GetHardwareRAMInfo(totalphys, availphys, totalvirt, availvirt);
   var ramGB : Integer = round(totalphys/1024) div 1000;

   newinfo := 'pur|'+IntToStr(gProfile.purchaseunixtime);
   newinfo := newinfo+'|dlc|'+IntToStr(gProfile.lastknowndlcs);
   newinfo := newinfo+'|ram|'+IntToStr(ramGB);

   if (SteamwrapIsSteamRunning) then
   begin
      var accountid, accinst, acctype, universe : Integer;
      SteamwrapGetSteamIDAcc(accountid, accinst, acctype, universe);
      var accountname : String = SteamwrapFriendsGetPersonaName;
      var validname : String;
      if (accountname<>'') then
      _misc_RemoveInvalidCharsInStringByFilter(accountname, gc_gui_charfilter_pass, validname);

      tmpclient.sic := accountid;
      tmpclient.snc := validname;
      if (accountid<>0) and (accountid<>tmpclient.si1) and (accountid<>tmpclient.si2) and (accountid<>tmpclient.si3) then
      begin
         // is new
         if (tmpclient.si1=0) then
         tmpclient.si1 := accountid
         else
         if (tmpclient.si2=0) then
         tmpclient.si2 := accountid
         else
         if (tmpclient.si3=0) then
         tmpclient.si3 := accountid;
      end;
      if (accountname<>'') then
      begin
         if (validname<>tmpclient.sn1) and (validname<>tmpclient.sn2) and (validname<>tmpclient.sn3) then
         begin
            // is new
            if (tmpclient.sn1='') then
            tmpclient.sn1 := validname
            else
            if (tmpclient.sn2='') then
            tmpclient.sn2 := validname
            else
            if (tmpclient.sn3='') then
            tmpclient.sn3 := validname;
         end;
      end;
   end;
   
   newinfo := newinfo+'|sic|'+IntToStr(tmpclient.sic);
   newinfo := newinfo+'|si1|'+IntToStr(tmpclient.si1);
   newinfo := newinfo+'|si2|'+IntToStr(tmpclient.si2);
   newinfo := newinfo+'|si3|'+IntToStr(tmpclient.si3);
   newinfo := newinfo+'|snc|'+tmpclient.snc;
   newinfo := newinfo+'|sn1|'+tmpclient.sn1;
   newinfo := newinfo+'|sn2|'+tmpclient.sn2;
   newinfo := newinfo+'|sn3|'+tmpclient.sn3;
end;

// _misc_SetupMapPlayersInfo
//
procedure _misc_SetupMapPlayersInfo();
begin
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      var pclient : Pointer = gInternetShell.clients.GetByLanID(gMap.players[i].lanid);
      if (pclient<>nil) then
      begin
         gMap.playersinfo[i].sic := TInternetShellClient(pclient).sic;
         gMap.playersinfo[i].si1 := TInternetShellClient(pclient).si1;
         gMap.playersinfo[i].si2 := TInternetShellClient(pclient).si2;
         gMap.playersinfo[i].si3 := TInternetShellClient(pclient).si3;
         gMap.playersinfo[i].snc := TInternetShellClient(pclient).snc;
         gMap.playersinfo[i].sn1 := TInternetShellClient(pclient).sn1;
         gMap.playersinfo[i].sn2 := TInternetShellClient(pclient).sn2;
         gMap.playersinfo[i].sn3 := TInternetShellClient(pclient).sn3;
      end
      else
      begin
         gMap.playersinfo[i].sic := 0;
         gMap.playersinfo[i].si1 := 0;
         gMap.playersinfo[i].si2 := 0;
         gMap.playersinfo[i].si3 := 0;
         gMap.playersinfo[i].snc := '';
         gMap.playersinfo[i].sn1 := '';
         gMap.playersinfo[i].sn2 := '';
         gMap.playersinfo[i].sn3 := '';
      end;
   end;
end;

// _misc_GenerateRoomDataAndMapName
//
procedure _misc_GenerateRoomDataAndMapName(psession : Pointer; var roomdata, mapname : String);
begin
   var bQuickPlayCustom : Boolean = gInternetShell.bautosearch or gInternetShell.bratingroom;

   var statetag, humancount, aicount, closecount, ping, rank : Integer;
   const cTxtNone = '0';
   const cTxtClosed = 'x';
   roomdata := '';
   mapname := '';
   // players info
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      var iready : Integer = _misc_SwitchInt(0, 1, gMap.players[i].bready);
      if (gMap.players[i].lanid<>0) then
      begin
         roomdata := roomdata+IntToStr(gMap.players[i].lanid)+','+IntToStr(gMap.players[i].cid)+','+IntToStr(gMap.players[i].team)+','+IntToStr(gMap.players[i].color)+','+IntToStr(iready)+gc_gui_delimitercharstr;
         humancount := humancount+1;
      end
      else
      if (gMap.players[i].bclosed) or ((bQuickPlayCustom) and (i>=(gint_quickplay_countofplayers div 2)) and (i<gint_quickplay_countofplayers)) then
      begin
         roomdata := roomdata+cTxtClosed+gc_gui_delimitercharstr;
         closecount := closecount+1;
      end
      else
      if (gMap.players[i].bai) then
      begin
         roomdata := roomdata+IntToStr(-gMap.players[i].aidifficulty)+','+IntToStr(gMap.players[i].cid)+','+IntToStr(gMap.players[i].team)+','+IntToStr(gMap.players[i].color)+gc_gui_delimitercharstr;
         aicount := aicount+1;
      end
      else
      roomdata := roomdata+cTxtNone+gc_gui_delimitercharstr;
   end;
   // generator settings
   roomdata := roomdata+IntToStr(gMap.settings.gen.season)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.gen.terraintype)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.gen.relieftype)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.gen.resourcestart)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.gen.resourcemines)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.gen.mapsize)+gc_gui_delimitercharstr;
   // additional settings
   roomdata := roomdata+IntToStr(gMap.settings.additional.startingunits)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.balloon)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.cannons)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.peacetime)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.century18)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.capture)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.marketdip)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.teams)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.autosave)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.limit)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.gamespeed)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.settings.additional.adviserassistant)+gc_gui_delimitercharstr;

   var battleind : Integer;
   if (not gMap.bbattle) then
   battleind := -1
   else
   battleind := gMap.battleind;
   roomdata := roomdata+IntToStr(battleind)+gc_gui_delimitercharstr;
   roomdata := roomdata+IntToStr(gMap.battlestage);

   if (psession<>nil) then
   begin
      var maxplayers : Integer = TInternetShellSession(psession).maxplayers;
      var bfull : Boolean = (maxplayers>0) and ((humancount+aicount+closecount)>=maxplayers);
      var blocked : Boolean = TInternetShellSession(psession).blocked;
      statetag := statetag or (1 shl 0);
      if (bfull) or (gInternetShell.bautosearch) then
      statetag := statetag or (1 shl 1);
      if (blocked) then
      statetag := statetag or (1 shl 2);
   end;
   mapname := IntToStr(statetag)+gc_gui_delimitercharstr+IntToStr(humancount)+gc_gui_delimitercharstr+IntToStr(aicount)+gc_gui_delimitercharstr+IntToStr(closecount)+gc_gui_delimitercharstr+IntToStr(ping)+gc_gui_delimitercharstr+IntToStr(rank);

   if (bQuickPlayCustom) then
   begin
      var qpstate : Integer;
      if gInternetShell.bautosearch then
      qpstate := 1;
      mapname := mapname+gc_gui_delimitercharstr+IntToStr(gMap.players[1].lanid)+gc_gui_delimitercharstr+IntToStr(gMap.players[2].lanid)+gc_gui_delimitercharstr+IntToStr(Round(gInternetShell.autosearchtotaltime))+gc_gui_delimitercharstr+IntToStr(qpstate);
   end;
end;

// _misc_GetGameNameAndPasswordBySession
//
procedure _misc_GetGameNameAndPasswordBySession(psession : Pointer; var visiblename, pass, roomchecksum : String);
begin
   if (psession<>nil) then
   begin
      var gamename : String = TInternetShellSession(psession).gamename;
      if (IsDelimiterCharExists(gamename, gc_gui_tabchar)) and (GetDelimiterStringCount(gamename, gc_gui_tabchar, gc_gui_quotechar)=3) then
      begin
         visiblename := GetDelimiterStringByIndex(gamename, gc_gui_tabchar, gc_gui_quotechar, 0);
         pass := GetDelimiterStringByIndex(gamename, gc_gui_tabchar, gc_gui_quotechar, 1);
         roomchecksum := GetDelimiterStringByIndex(gamename, gc_gui_tabchar, gc_gui_quotechar, 2);
      end
      else
      visiblename := gamename;
   end;
end;

// _misc_InternetShellUpdateCurrentSessionName(
//
function _misc_InternetShellUpdateCurrentSessionName(var internetshell : TInternetShell; bLan : Boolean) : Boolean;
begin
   Result := False;
   //Log('UpdateCurrentSessionName');
   if (internetshell.currentsessionid>0) then
   begin
      var psession : Pointer = internetshell.sessions.GetByMasterID(internetshell.currentsessionid);
      if (psession<>nil) then
      begin
         internetshell.currentsession := TInternetShellSession(psession);
         var newmapname : String;
         var newroomdata : String;
         var prevmapname : String = internetshell.currentsession.mapname;
         var prevroomdata : String = internetshell.roomdata;

         var i, j : Integer;
         for i:=0 to gc_MaxPlayerCount-1 do
         begin
            if (gMap.players[i].lanid<>0) then
            begin
               var bexists : Boolean;
               for j:=0 to LanGetClientsCount-1 do
               begin
                  var clientid : Integer = LanGetClientIDByIndex(j);
                  if (gMap.players[i].lanid=clientid) then
                  begin
                     bexists := True;
                     break;
                  end;
               end;
               if (not bexists) then
               _map_ResetMapPlayer(gMap.players[i], i, (gInternetShell.bratingroom or gInternetShell.bautosearch));
            end;
         end;
         for i:=0 to LanGetClientsCount-1 do
         begin
            var clientid : Integer = LanGetClientIDByIndex(i);
            if (clientid<>0) then
            begin
               var pclient : Pointer = internetshell.clients.GetByLanID(clientid);
               if (pclient<>nil) then
               begin
                  var bexists, bplaced : Boolean;

                  for j:=0 to gc_MaxPlayerCount-1 do
                  if (clientid=gMap.players[j].lanid) then
                  begin
                     bexists := True;
                     break;
                  end;

                  if (not bexists) then
                  for j:=0 to gc_MaxPlayerCount-1 do
                  if (not gMap.players[j].bclosed) and (not gMap.players[j].bai) and (not gMap.players[j].bhuman) then
                  begin
                     if (j=(gc_MaxPlayerCount-1)) then
                     begin
                        if (_net_IsServer) then
                        begin
                           log('UpdateCurrentSessionName : 8th player not yet supported, kick clientid = '+IntToStr(clientid));
                           LanKillClient(clientid);
                        end;
                        continue;
                     end;
                     gMap.players[j].name := TInternetShellClient(pclient).nick;
                     gMap.players[j].lanid := clientid;
                     gMap.players[j].bhuman := True;
                     
                     if (gMap.bbattle) then
                     begin
                        var k, teamcount1, teamcount2 : Integer;
                        for k:=0 to gc_MaxPlayerCount-1 do
                        begin
                           if gMap.players[k].bexists and gMap.players[k].bhuman then
                           begin
                              if (gMap.players[k].team=1) then
                              teamcount1 := teamcount1+1;
                              if (gMap.players[k].team=2) then
                              teamcount2 := teamcount2+1;
                           end;
                        end;
                        if (teamcount1>teamcount2) then
                        gMap.players[j].team := 2
                        else
                        gMap.players[j].team := 1;
                     end;

                     bplaced := True;
                     break;
                  end;
                  if (not bexists) and (not bplaced) then
                  Log('UpdateCurrentSessionName not bexists and not bplaced or 8th player kicked');
               end;
            end;
         end;
         _misc_GenerateRoomDataAndMapName(psession, newroomdata, newmapname);
         if (prevmapname<>newmapname) then
         begin
            internetshell.currentsession.mapname := newmapname;
            TInternetShellSession(psession).mapname := newmapname;
            LanSrvSetMapFile(newmapname);
         end;
         gInternetShell.roomdata := newroomdata;
         Result := (prevroomdata<>newroomdata);
         if (prevroomdata<>newroomdata) and (bLan) then
         begin
            var pLan : Integer = _parser_ParserTemporary(True);
            ParserSetValueByKeyByHandle(pLan, 's', newroomdata);
            LanPublicServerSendSessionParser(0, gc_LAN_ROOM_SERVER_DATASYNC, pLan);
            // TODO gc_LAN_ROOM_SERVER_DATACHANGE (find only difference and send them
         end;
      end;
   end;
end;

// _misc_InternetShellRefreshCurrentSession
//
procedure _misc_InternetShellRefreshCurrentSession(var internetshell : TInternetShell; bSetgMapFromName : Boolean);
begin
   //Log('RefreshCurrentSession : bSetgMapFromName='+BoolToStr(bSetgMapFromName));
   procedure SetMapFromName(var map : TMap; name : String);
   begin
      const cDelim44 = 44;
      const cTxtNone = '0';
      const cTxtClosed = 'x';
      if (IsDelimiterCharExists(name, gc_gui_delimiterchar)) then
      begin
         var count : Integer = GetDelimiterStringCount(name, gc_gui_delimiterchar, gc_gui_quotechar);
         var i, j : Integer;
         for i:=0 to count-1 do
         begin
            var str : String = GetDelimiterStringByIndex(name, gc_gui_delimiterchar, gc_gui_quotechar, i);
            var istr : Integer = StrToInt(str);
            case i of
               0..gc_MaxPlayerCount-1 : begin
                  _map_ResetMapPlayer(map.players[i], i, (gInternetShell.bratingroom or gInternetShell.bautosearch));
                  if IsDelimiterCharExists(str, cDelim44) then
                  begin
                     var c : Integer = GetDelimiterStringCount(str, cDelim44, gc_gui_quotechar);
                     var bHuman : Boolean = StrToInt(GetDelimiterStringByIndex(str, cDelim44, gc_gui_quotechar, 0))>0;
                     if (bHuman) then
                     begin
                        for j:=0 to c-1 do
                        begin
                           var val : Integer = StrToInt(GetDelimiterStringByIndex(str, cDelim44, gc_gui_quotechar, j));
                           case j of
                              0 : begin
                                 map.players[i].lanid := val;
                                 var pclient : Pointer = internetshell.clients.GetByLanID(val);
                                 if (pclient<>nil) then
                                 map.players[i].name := TInternetShellClient(pclient).nick;
                              end;
                              1 : map.players[i].cid := val;
                              2 : map.players[i].team := val;
                              3 : map.players[i].color := val;
                              4 : map.players[i].bready := (val=1);
                           end;
                        end;
                        map.players[i].bhuman := True;
                     end
                     else
                     begin
                        for j:=0 to c-1 do
                        begin
                           var val : Integer = StrToInt(GetDelimiterStringByIndex(str, cDelim44, gc_gui_quotechar, j));
                           case j of
                              0 : map.players[i].aidifficulty := -val; // value is inverted
                              1 : map.players[i].cid := val;
                              2 : map.players[i].team := val;
                              3 : map.players[i].color := val;
                              4 : map.players[i].bready := False; // doesnt matter
                           end;
                        end;
                        map.players[i].bai := True;
                     end;
                     map.players[i].bexists := True;
                  end
                  else
                  if (str=cTxtClosed) then
                  map.players[i].bclosed := True
                  else
                  if (str=cTxtNone) then
                  begin
                     map.players[i].bclosed := False;
                     map.players[i].bai := False;
                     map.players[i].bhuman := False;
                     map.players[i].bexists := False;
                  end
                  else
                  ErrorLog('SetMapFromName : unknown value = '+str);
               end;
               gc_MaxPlayerCount  : map.settings.gen.season := istr;
               gc_MaxPlayerCount+1 : map.settings.gen.terraintype := istr;
               gc_MaxPlayerCount+2 : map.settings.gen.relieftype := istr;
               gc_MaxPlayerCount+3 : map.settings.gen.resourcestart := istr;
               gc_MaxPlayerCount+4 : map.settings.gen.resourcemines := istr;
               gc_MaxPlayerCount+5 : map.settings.gen.mapsize := istr;
               gc_MaxPlayerCount+6 : map.settings.additional.startingunits := istr;
               gc_MaxPlayerCount+7 : map.settings.additional.balloon := istr;
               gc_MaxPlayerCount+8 : map.settings.additional.cannons := istr;
               gc_MaxPlayerCount+9 : map.settings.additional.peacetime := istr;
               gc_MaxPlayerCount+10 : map.settings.additional.century18 := istr;
               gc_MaxPlayerCount+11 : map.settings.additional.capture := istr;
               gc_MaxPlayerCount+12 : map.settings.additional.marketdip := istr;
               gc_MaxPlayerCount+13 : map.settings.additional.teams := istr;
               gc_MaxPlayerCount+14 : map.settings.additional.autosave := istr;
               gc_MaxPlayerCount+15 : map.settings.additional.limit := istr;
               gc_MaxPlayerCount+16 : map.settings.additional.gamespeed := istr;
               gc_MaxPlayerCount+17 : map.settings.additional.adviserassistant := istr;
               gc_MaxPlayerCount+18 : begin
                  map.bbattle := (istr>=0);
                  if (map.bbattle) then
                  map.battleind := istr;
               end;
               gc_MaxPlayerCount+19 : map.battlestage := istr;
               else
               ErrorLog('_misc_InternetShellUpdateCurrentSessionName : out of range');
            end;
         end;
      end;
   end;

   var i, j : Integer;
   if (internetshell.currentsessionid>0) then
   begin
      var psession : Pointer = internetshell.sessions.GetByMasterID(internetshell.currentsessionid);
      if (psession<>nil) then
      begin
         internetshell.currentsession := TInternetShellSession(psession);
         if (bSetgMapFromName) then
         SetMapFromName(gMap, gInternetShell.roomdata);
      end;
   end;
end;

// _misc_PrepareDestroyGameObject
//
procedure _misc_PrepareDestroyGameObject(const hnd: Integer);
begin
   var pobj: Pointer=_unit_GetTObj(hnd);
   if (pobj<>nil) then
   begin
      var cid : Integer = TObj(pobj).cid;
      var id : Integer = TObj(pobj).id;
      var pobjprop: Pointer=gObjProp[cid][id];
      if (pobjprop<>nil) then
      begin
         if (TObjProp(pobjprop).bbuilding) then
         begin
            SetGameObjectColIntMaskOptionsByHandle(hnd, True, False);

            if GetProjectModeInt <> gc_project_mode_none then
            _misc_UnitTopologyUpdate(hnd);

            GameObjectDestroyDecalByHandle(hnd);
            EffectClear(hnd); // check if we need it to prevent some crashes on destroy game object
            gGuiUpdateHighlights.Remove(hnd); // prevent crash on gate upgrade, when we destroy wall segments

            var top : Integer = _gui_GetTop;
            if (top<>0) then
            begin
               var hudCollection : Integer = GetGUIElementIndexByNameParent('hud', top);
               if (hudCollection<>0) then
               begin
                  var hudHnd : Integer = GetGUIGroupHUDCollectionItemByGameObject(hudCollection, hnd);
                  if (hudHnd<>0) then
                  GUIGroupHUDCollectionDeleteGameObject(hudCollection, hnd);
               end;
            end;
         end;
      end;
   end;
end;

// _misc_SetMinimapVisible
//
procedure _misc_SetMinimapVisible(const bVisible : Boolean);
begin
   SetGUIMiniMapVisible(bVisible);
   var minimapHnd : Integer = GetGUIElementIndexByNameParent('minimap', _gui_GetTop);
   if (minimapHnd<>0) then
   SetGUIElementVisible(minimapHnd, bVisible);
end;

// _misc_CleanRecordManagerExt
//
procedure _misc_CleanRecordManagerExt();
begin
   var oldgametime : Float = GetGameTime;
   CleanRecordManager;
   var newgametime : Float = GetGameTime;
   var deltatime : Float = oldgametime-newgametime;
   var i, j, k : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      gPlayer[i].aidata.inittime := Clamp(gPlayer[i].aidata.inittime, 0, MaxFloat(0, gPlayer[i].aidata.inittime-deltatime));
      gPlayer[i].lastprogresstime := Clamp(gPlayer[i].lastprogresstime, 0, MaxFloat(0, gPlayer[i].lastprogresstime-deltatime));
      for j:=0 to gPlayer[i].aiData.armyList.GetCount-1 do
      begin
         var pArmy : Pointer = gPlayer[i].aiData.armyList.Get(j);
         if (pArmy<>nil) then
         TArmy(pArmy).fLastBattleTime := Clamp(TArmy(pArmy).fLastBattleTime, 0, MaxFloat(0, TArmy(pArmy).fLastBattleTime-deltatime));
      end;
      for j:=0 to gc_MaxCountryCount-1 do
      for k:=0 to gc_country_maxupgradecount-1 do
      gPlayer[i].upgstate[j][k].timedone := Clamp(gPlayer[i].upgstate[j][k].timedone, 0, MaxFloat(0, gPlayer[i].upgstate[j][k].timedone-deltatime));
   end;
   for i:=0 to gc_MaxProjSimulatePoints-1 do
   gProjSimulatePoints.projsim[i].damagetime := Clamp(gProjSimulatePoints.projsim[i].damagetime, 0, MaxFloat(0, gProjSimulatePoints.projsim[i].damagetime-deltatime));
   for i:=0 to GetCountOfPlayers-1 do
   begin
      var plHnd : Integer = GetPlayerHandleByIndex(i);
      for j:=0 to GetPlayerGameObjectsCountByHandle(plHnd)-1 do
      begin
         var goHnd : Integer = GetGameObjectHandleByIndex(j, plHnd);
         if (_misc_GetBaseObjID(goHnd)=gc_baseid_obj) then
         begin
            var pobj : Pointer = _misc_GetObjectArgData(goHnd, gc_argunit_obj);
            if (pobj<>nil) then
            begin
               TObj(pobj).lastprogresstime := Clamp(TObj(pobj).lastprogresstime, 0, MaxFloat(0, TObj(pobj).lastprogresstime-deltatime));
               TObj(pobj).standtime := Clamp(TObj(pobj).standtime, 0, MaxFloat(0, TObj(pobj).standtime-deltatime));
               TObj(pobj).lasttimecheckcapture := random*gc_unit_TimeCheckCapture;
               TObj(pobj).lasttimeidlegrid := random*gc_unit_TimeIdleGrid;
               TObj(pobj).lasttimescangrid := random*gc_unit_TimeScanGrid;
               TObj(pobj).lasttimetopology := random*gc_unit_TimeTopology;
               TObj(pobj).lasttimebestposition := random*gc_unit_TimeFindBestPosition;
               TObj(pobj).lastsearchenemy := random;
            end;
         end
         else
         if (_misc_GetBaseObjID(goHnd)=gc_baseid_res) then
         begin
            var pres : Pointer = _misc_GetObjectArgData(goHnd, gc_argenv_res);
            if (pres<>nil) then
            TRes(pres).timetransormlast := Clamp(TRes(pres).timetransormlast, 0, MaxFloat(0, TRes(pres).timetransormlast-deltatime));
         end;
      end;
   end;

   for i:=0 to gc_scenario_maxtriggers-1 do
   gScenario.triggers[i].activetime := Clamp(gScenario.triggers[i].activetime, 0, MaxFloat(0, gScenario.triggers[i].activetime-deltatime));
   for i:=0 to gc_scenario_maxtimers-1 do
   begin
      gScenario.timers[i].starttime := Clamp(gScenario.timers[i].starttime, 0, MaxFloat(0, gScenario.timers[i].starttime-deltatime));
      gScenario.timers[i].time := Clamp(gScenario.timers[i].time, 0, MaxFloat(0, gScenario.timers[i].time-deltatime));
   end;

   gProgress.lastprogresstime := Clamp(gProgress.lastprogresstime, 0, MaxFloat(0, gProgress.lastprogresstime-deltatime));
   gProgress.soundlastprogresstime := Clamp(gProgress.soundlastprogresstime, 0, MaxFloat(0, gProgress.soundlastprogresstime-deltatime));
end;

// _misc_SetUseCollision
//
procedure _misc_SetUseCollision(val: boolean);
begin
   gbool_use_collision:=val;
   var pind: integer;
   for pind:=0 to gc_MaxPlayerCount-1 do begin
      var gind: integer;
      var phnd: integer=GetPlayerHandleByIndex(pind);
      for gind:=0 to GetPlayerGameObjectsCountByHandle(phnd)-1 do begin
         var ghnd: integer=GetGameObjectHandleByIndex(gind, phnd);
         if _unit_IsUnit(ghnd) then begin
            if gbool_use_collision then begin
               SetGameObjectCollidedStateNameByHandle(ghnd, '_misc_Collided');
               SetGameObjectUncollidedStateNameByHandle(ghnd, ''); // leave empty
               SetGameObjectRuleCollidedExecFrByHandle(ghnd, 4, 35.0, False); // no collide event with friendly
               SetGameObjectRuleCollidedExecEnByHandle(ghnd, 2, 90.0, False); // collide event with enemy in front for 90 fov
               SetGameObjectRuleCollidedExecNlByHandle(ghnd, 4, 35.0, False); // no collide event with neutral
               end else begin
               SetGameObjectCollidedStateNameByHandle(ghnd, '');
               SetGameObjectUncollidedStateNameByHandle(ghnd, '');
               SetGameObjectRuleCollidedExecFrByHandle(ghnd, 4, 90.0, False); // no collide event with friendly
               SetGameObjectRuleCollidedExecEnByHandle(ghnd, 4, 90.0, False); // no collide event with enemy
               SetGameObjectRuleCollidedExecNlByHandle(ghnd, 4, 90.0, False); // no collide event with neutral
            end;
         end;
      end;
   end;
end;

// _misc_ProcessMessage
//
procedure _misc_ProcessMessage(var text : String);
begin
   const cSpaceChar  = 32; // space
   const cQuoteChar  = 34; // "
   const cEscChar    = 27; // Escape
   const cReturnChar = 13; // Enter or Return
   const cUpChar     = 38; // key up
   const cDownChar   = 40; // key down
   var s1, s2, s3, s4 : String;
   if IsDelimiterCharExists(text, cSpaceChar) then
   _misc_GetDelimiterString(text, cSpaceChar, s1, s2, s3, s4)
   else
   s1 := text;

   var plhnd : Integer = GetPlayerHandleInterfaceIO;
   var plInd : Integer = GetPlayerIndexByHandle(plhnd);
   var bvalid, bstate : Boolean;
   _misc_CheckStringValidBool(s2, bvalid, bstate);

   if _net_IsOnline or _net_IsRecord then
   begin
      if (s1='net') then
      begin
         var parg: Pointer = StateMachineGetArgDataByInd(GetPlayerStateMachineHandle(plhnd), gc_argplayer_plr);
         if parg<>nil then begin
            var str : String = text;
            //str := SubStr(str, 5, StrLength(str)-4);
            TPlayerArgs(parg).fbasename := str;
            PlayerExecuteStateByHandle(plhnd, 'WritePackage');
         end;
      end;
   end;

   var bMPCheck : Boolean = (LanMyInfoID=1) or ((_net_IsOffline) or (LanGetClientsCount<=1));
   if (bMPCheck) or (s1='perf') or (s1='debug') or (s1='fps') or (text='www false') or (s1='proctofile') or (StrExists(s1, 'saverep')) or ((gMap.bbattle) and (text='scenario start')) then
   case s1 of
      'res' : begin
         if ((_net_IsOffline) or (_net_IsServer)) then
         begin
            gProfile.bNoAchs := True;
            if (s2<>'') and (s3<>'') then
            begin
               var resid : Integer = -1;
               case s2 of
                  'food' : resid := gc_resource_type_food;
                  'wood' : resid := gc_resource_type_wood;
                  'stone' : resid := gc_resource_type_stone;
                  'gold' : resid := gc_resource_type_gold;
                  'iron' : resid := gc_resource_type_iron;
                  'coal' : resid := gc_resource_type_coal;
               end;
               if (resid>0) then
               _res_SetResToPlayerByIndex(plInd, resid, StrToInt(s3))
               else
               if (s2='all') then
               begin
                  for resid:=1 to 6 do
                  _res_SetResToPlayerByIndex(plInd, resid, StrToInt(s3))
               end;
               _misc_LanForceSyncRes;
            end;
         end
         else
         ErrorLog('Can not apply res cheat. You are not IsOffline and not IsServer');
      end;
      'ai' : begin
         gProfile.bNoAchs := True;
         if (s3<>'') then
         begin
            _misc_CheckStringValidBool(s3, bvalid, bstate);
            var paidata : Pointer = gPlayer[plInd].aidata;
            case s2 of
               'difficulty' : begin
                  if (s3<>'') then
                  gPlayer[plInd].difficulty := StrToInt(s3);
                  Log('current player difficulty = '+gc_gui_space+s2+gc_gui_space+IntToStr(gPlayer[plInd].difficulty));
               end;
               'smart' : begin
                  if (bvalid) then
                  begin
                     if (not gPlayer[plInd].bai) and (bstate) then
                     _player_UpdateAIData(plInd, True);
                     gPlayer[plInd].bai := bstate;
                     TAIData(paidata).bhumanai := True;
                     TAIData(paidata).bprogressEconomy := True;
                     TAIData(paidata).bprogressConstruction := False;
                     TAIData(paidata).bprogressProduce := False;
                     TAIData(paidata).bprogressUpgrades := False;
                     TAIData(paidata).bprogressWar := False;
                  end
                  else
                  ErrorLog('invalid bflag');
               end;
               'upgrade' :begin
                  if (bvalid) then
                  TAIData(paidata).bprogressUpgrades := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bprogressUpgrades := (not TAIData(paidata).bprogressUpgrades);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bprogressUpgrades));
               end;
               'human' : begin
                  if (bvalid) then
                  TAIData(paidata).bhumanai := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bhumanai := (not TAIData(paidata).bhumanai);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bhumanai));
               end;
               'economy' : begin
                  if (bvalid) then
                  TAIData(paidata).bprogressEconomy := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bprogressEconomy := (not TAIData(paidata).bprogressEconomy);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bprogressEconomy));
               end;
               'construct' : begin
                  if (bvalid) then
                  TAIData(paidata).bprogressConstruction := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bprogressConstruction := (not TAIData(paidata).bprogressConstruction);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bprogressConstruction));
               end;
               'produce' : begin
                  if (bvalid) then
                  TAIData(paidata).bprogressProduce := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bprogressProduce := (not TAIData(paidata).bprogressProduce);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bprogressProduce));
               end;
               'war' : begin
                  if (bvalid) then
                  TAIData(paidata).bprogressWar := bstate
                  else
                  if (s3='') then
                  TAIData(paidata).bprogressWar := (not TAIData(paidata).bprogressWar);
                  Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2+gc_gui_space+BoolToStr(TAIData(paidata).bprogressWar));
               end;
            end;
         end
         else
         begin
            if (s2='difficulty') then
            begin
               if (s3<>'') then
               gPlayer[plInd].difficulty := StrToInt(s3);
               Log('current player difficulty = '+gc_gui_space+s2+gc_gui_space+IntToStr(gPlayer[plInd].difficulty));
            end
            else
            if (s2='update') then
            begin
               _player_UpdateAIData(plInd, True);
               Log('ai plind='+IntToStr(plInd)+gc_gui_space+s2);
            end
            else
            begin
               if (bvalid) then
               gPlayer[plInd].bai := bstate
               else
               if (s2='') then
               gPlayer[plInd].bai := (not gPlayer[plInd].bai);
               if (gScenario.bexists) then
               begin
                  gScenario.players[plInd].bai := gPlayer[plInd].bai;
                  gScenarioTmp.players[plInd].bai := gPlayer[plInd].bai;
               end;
               if (gPlayer[plInd].bai) then
               begin
                  if (not gPlayer[plind].bexists) then
                  gPlayer[plind].bexists := (gPlayer[plInd].counter.farmused>0);
                  _player_UpdateAIData(plInd, True);
               end;
               Log('ai plind='+IntToStr(plInd)+gc_gui_space+BoolToStr(gPlayer[plInd].bai));
            end;
         end;
      end;
      'peacemode' : begin
         gProfile.bNoAchs := True;
         if (bvalid) then
         begin
            if (gbool_editor_peacemode<>bstate) then
            EmulateGUIShortcut('Ctrl+W');
         end
         else
         if (s2='') then
         EmulateGUIShortcut('Ctrl+W');
      end;
      'fow' : begin
         gProfile.bNoAchs := True;
         if (bvalid) then
         begin
            if (gbool_editor_fogofwar<>bstate) then
            EmulateGUIShortcut('Ctrl+F');
         end
         else
         if (s2='') then
         EmulateGUIShortcut('Ctrl+F');
      end;
      'minimap' : begin
         if (bvalid) then
         begin
            if (GetGUIMiniMapVisible<>bstate) then
            EmulateGUIShortcut('interface|minimap');
         end
         else
         if (s2='') then
         EmulateGUIShortcut('interface|minimap');
      end;
      'freecamera', 'screenshotcamera', 'screenshotmode' : begin
         if (bvalid) then
         gbool_gui_screenshotcamera := bstate
         else
         gbool_gui_screenshotcamera := not gbool_gui_screenshotcamera;
         NeedRestartGUI;
      end;
      'col' : begin
         if (bvalid) then
         _misc_SetUseCollision(bstate)
         else
         _misc_SetUseCollision(not gbool_use_collision);
      end;
      'scenario' : begin
         var prevstate : Boolean = gScenario.bactive;
         case s2 of
            'run', 'start', 'active' : begin
               gScenario.bactive := True;
               gbool_editor_peacemode := False;
               gInterface.gamemode := gc_gamemode_game;
            end;
            'stop' : gScenario.bactive := False;
            'win' : begin
               gPlayer[0].victorystate := gc_player_victorystate_win;
               exit;
            end;
            'lose' : begin
               gPlayer[0].victorystate := gc_player_victorystate_lose;
               exit;
            end;
            '' : gScenario.bactive := not gScenario.bactive;
            else
            exit;
         end;
         if (gScenario.bactive) then
         begin
            var i : Integer;
            if (not prevstate) then
            begin
               _misc_CleanRecordManagerExt;
               for i:=0 to gc_MaxPlayerCount-1 do
               gPlayer[i].difficulty := gProfileUserStruct.campaign.difficulty;
               gScenario.difficulty := gProfileUserStruct.campaign.difficulty;
            end;
            if (gMap.bbattle) then
            begin
               for i:=0 to gc_MaxPlayerCount-1 do
               gPlayer[i].bexists := gMap.players[i].bexists;
            end
            else
            for i:=0 to gc_MaxPlayerCount-1 do
            gPlayer[i].bexists := (gPlayer[i].counter.farmused>0);
            
            // 0 player is always bexists
            if (_net_IsOffline) then
            begin
               gPlayer[0].bexists := True;
               gMap.players[0].name := gProfile.name;

               for i:=1 to gc_MaxPlayerCount-1 do
               begin
                  var text : String;
                  var namekey : String = gScenario.players[i].name;
                  if (namekey<>'') then
                  begin
                     text := GetLocaleTableListItemByID(gScenario.settings.langtable, namekey);
                     if (text='') then
                     begin
                        namekey := StrReplace(namekey, ' ', '');
                        text := GetLocaleTableListItemByID(gScenario.settings.langtable, namekey);
                     end;
                  end;
                  if (text='') then
                  text := namekey;
                  gMap.players[i].name := text;
               end;
            end;
            if (gScenario.settings.filename<>'') then
            gMap.name := ExtractFileName(gScenario.settings.filename);
            gPlayer[0].victorystate := gc_player_victorystate_none;
            gPlayer[0].victorystategametime := 0;
            for i:=0 to gc_scenario_maxqueries-1 do
            gScenario.queries[i].langtable := gScenario.settings.langtable;
            for i:=0 to gc_scenario_maxresults-1 do
            gScenario.results[i].langtable := gScenario.settings.langtable;
            for i:=0 to gc_scenario_maxobjectives-1 do
            gScenario.objectives[i].langtable := gScenario.settings.langtable;
            _scenario_UpdateQueries(gScenario);
            _scenario_UpdateAIRegions(gScenario);
            _scenario_UpdateAIRegionsActiveStateToTriggers(gScenario);
            if (gbool_editor_peacemode) then
            EmulateGUIShortcut('Ctrl+W');
            _res_ResGridClear;
            _res_ResGridResetEnvironment;
            var j : Integer;
            for i:=0 to gc_MaxPlayerCount-1 do
            for j:=0 to gc_ResCount-1 do
            _res_SetResToPlayerByIndex(i, j, gScenario.players[i].resources[j]);
            gScenario.triggers[0].bactive := true;

            var smHnd : Integer = GetTriggerManagerStateMachineHandle('trigger0');
            if (smHnd<>0) then
            StateMachineExecuteState(smHnd, 'Initial', 0);
            
            if _net_IsOnline then
            begin
               var progressPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_progress);
               if (progressPlHnd<>0) then
               begin
                  Log('PlayerExecuteStateByHandle(progressPlHnd, Scenario) : on scenario start');
                  PlayerExecuteStateByHandle(progressPlHnd, 'Scenario');
               end;
            end;
         end;
      end;
      'save', 'savemap': begin
         var filename: String=s2;
         if filename='' then
         filename:=ExtractFileName(GetCurrentMapFileName);
         var ext: String=ExtractFileExt(filename);
         if ext='' then
         filename:=filename+'.map';
         _misc_SaveMap(filename, false);
      end;
      'load', 'loadmap': begin
         var filename: String=s2;
         if filename='' then
         filename:=ExtractFileName(GetCurrentMapFileName);
         var ext: String=ExtractFileExt(filename);
         if ext='' then
         filename:=filename+'.map';
         _misc_LoadMap(filename);
      end;
      'changeplayer' : begin
         gProfile.bNoAchs := True;
         if (s2<>'') then
         begin
            var trgPlInd : Integer = StrToInt(s2);
            if (trgPlInd>=0) and (trgPlInd<gc_MaxPlayerCount) then
            begin
               var trgPlHnd : Integer = GetPlayerHandleByIndex(trgPlInd);
               var i, j, k : Integer;
               for i:=gc_MaxPlayerCount-1 downto 0 do
               begin
                  if (i<>trgPlInd) then
                  begin
                     var plhnd : Integer = GetPlayerHandleByIndex(i);
                     var lastsquad : Integer = -1;
                     var lastplayer : Integer = -1;
                     var aloneUnitsList : TIntegerList;
                     var squadsIndexList : TIntegerList;
                     var squadsPlayerIndexList : TIntegerList;
                     for j:=GetPlayerGameObjectsCountByHandle(plhnd)-1 downto 0 do
                     begin
                        var goHnd : Integer = GetGameObjectHandleByIndex(j, plhnd);
                        if (GetGameObjectPickedByHandle(goHnd)) then
                        if (_misc_GetBaseObjID(goHnd)=gc_baseid_obj) then
                        begin
                           var pobj : Pointer = _unit_GetTObj(goHnd);
                           if (pobj<>nil) then
                           begin
                              if (TObj(pobj).squad<0) then
                              aloneUnitsList.Add(goHnd)
                              else
                              if (TObj(pobj).squad<>lastsquad) or (TObj(pobj).pl<>lastplayer) then
                              begin
                                 lastsquad := TObj(pobj).squad;
                                 lastplayer := TObj(pobj).pl;
                                 var indof : Integer = squadsIndexList.IndexOf(TObj(pobj).squad);
                                 if (indof=-1) or (squadsPlayerIndexList.Get(indof)<>TObj(pobj).pl) then
                                 begin
                                    squadsIndexList.Add(TObj(pobj).squad);
                                    squadsPlayerIndexList.Add(TObj(pobj).pl);
                                 end;
                              end;
                           end;
                        end;
                     end;
                     if (squadsIndexList.GetCount>0) or (squadsPlayerIndexList.GetCount>0) then
                     _misc_SortIntegerLists2D(squadsIndexList, squadsPlayerIndexList, False);
                     for j:=squadsIndexList.GetCount-1 downto 0 do
                     begin
                        var plInd : Integer = squadsPlayerIndexList.Get(j);
                        var sqInd : Integer = squadsIndexList.Get(j);
                        for k:=gPlayer[plInd].squads.GetCount-1 downto 0 do
                        begin
                           var psquad : Pointer = gPlayer[plInd].squads.Get(k);
                           if (psquad<>nil) and (TSquad(psquad).fIndex=sqInd) then
                           begin
                              _misc_SquadChangePlayer(psquad, trgPlInd);
                              break;
                           end;
                        end;
                     end;
                     for j:=0 to aloneUnitsList.GetCount-1 do
                     begin
                        var goHnd : Integer = aloneUnitsList.Get(j);
                        _misc_ChangePlayer(goHnd, trgPlHnd, False, True, True);
                     end;
                  end;
               end;
            end;
         end
         else
         ErrorLog('command require parameter');
      end;
      'ssao' : begin
         if (bvalid) then
         SetSSAOEnable(bstate)
         else
         if (s2='') then
         SetSSAOEnable(not GetSSAOEnable);
      end;
      'fxaa' : begin
         if (bvalid) then
         SetFXAAEnable(bstate)
         else
         if (s2='') then
         SetFXAAEnable(not GetFXAAEnable);
      end;
      'vsync' : begin
         var smode : String;
         if (bvalid) then
         begin
            if (bstate) then
            smode := 'vsmSync'
            else
            smode := 'vsmNoSync';
            SetVSyncMode(smode)
         end
         else
         if (s2='') then
         begin
            if (GetVSyncMode='vsmNoSync') then
            smode := 'vsmSync'
            else
            smode := 'vsmNoSync';
            SetVSyncMode(smode)
         end;
      end;
      'video' : begin
         if s2='' then s2 := 'max';
         if (s2<>'') then
         begin
            if (s2='max') then
            begin
               SetSSAOEnable(True);
               SetFXAAEnable(True);
            end
            else
            if (s2='min') then
            begin
               SetSSAOEnable(False);
               SetFXAAEnable(False);
            end;
         end;
      end;
      'displaymode' : begin
         if (s2<>'') then
         begin
            var mode : Integer = StrToInt(s2);
            var smode : String;
            case mode of
               0 : smode := 'dmWindowFullScreen';
               1 : smode := 'dmFullScreen';
               2 : smode := 'dmWindow';
            end;
            if (smode<>'') then
            begin
               SetDisplayMode(smode);
               NeedRestartGUI;
            end;
         end;
      end;
      'showallmissions' : begin
         var newstate : Boolean;
         if (bvalid) then
         newstate := bstate
         else
         newstate := True;
         gProfile.bCheatShowAllMissions := newstate;
      end;
      'eval' : begin
         if (s2='') then
         EvaluateFileThread('eval.txt')
         else
         EvaluateFileThread(s2);
      end;
      'season' : begin
         var season : Integer = GetSeasonType;
         if (season=2) then
         _misc_SetSeason(0)
         else
         _misc_SetSeason(2);
      end;
      'www' : begin
         var newstate : Boolean;
         if (bvalid) then
         newstate := bstate
         else
         newstate := True;
         if (newstate) then
         gProfile.bNoAchs := True;
         gProfile.bCheatPlaceUnits := newstate;
         gProfile.bCheatNoFogOfWar := newstate;
         gProfile.bCheatSwitchPlayer := newstate;
         gProfile.bCheatPeaceMode := newstate;
         gProfile.bCheatNoSpeedLimit := newstate;
         gProfile.bCheatCopyPaste := newstate;
         gProfile.bCheatMoveUnits := newstate;
         gProfile.bShowFPS := newstate;
      end;
      'proctofile' : begin
         var pProcess : Integer = _parser_ParserTemporary(True);
         ProcessesToParserStruct(pProcess);
         ParserSaveToFileByHandle(pProcess, GetAppSaveDirectoryPath+'processlist.txt');
      end;
      'modsave' : begin
         var pMods : Integer = _parser_ParserTemporary(True);
         var name : String;
         if s2='' then
         name := 'mod'
         else
         name := s2;
         ModLibraryGetParser(pMods, True);
         ParserSaveToFileByHandle(pMods, name);
      end;
      'modsync' : begin
         var name : String;
         if s2='' then
         name := 'mod'
         else
         name := s2;
         if IsFileExists(name) then
         begin
            if not SteamIsWorkshopPrepared then
            SteamPrepareWorkshop(false, true);
            if SteamIsWorkshopPrepared then
            begin
               LanShutdown;
               SteamWorkshopCallback(StateMachineGetGUISMHandle, '', '', '', '', 'onworkshopautoupdate');

               StringRegister7 := gstring_checksum;
               // save my previous mod list start
               var restorefile : String = GetAppSaveDirectoryPath+'mods.restore';
               var brestoreexists : Boolean = IsFileExists(restorefile);
               var pModList : Integer = _parser_ParserTemporary(True);
               if (not brestoreexists) then
               ModLibraryGetParser(pModList, False)
               else
               ParserLoadFromFileByHandle(pModList, restorefile);
               var pModListTemp : Integer = ParserAddChildByIndex(pModList, gc_parsername_tmp);
               ParserSetValueByKeyByHandle(pModListTemp, 'checksum', StringRegister7);
               ParserSaveToFileByHandle(pModList, restorefile);
               // save finish

               var pSyncs : Integer = _parser_ParserTemporary(True);
               ParserLoadFromFileByHandle(pSyncs, name);
               if SteamSyncWorkshopItems(pSyncs)=0 then
               GUIExecuteState('onworkshopautoupdate');
               _gui_ShowConnectStateMessage(gc_ConnectModsState_SyncInProgress);
            end;
         end;
      end;
      'videoinfo' : begin
         var name : String;
         if s2='' then
         name := 'videocontroller.txt'
         else
         name := s2;
         var pParser : Integer = _parser_ParserTemporary(True);
         GetWin32VideoControllerInfo(pParser);
         ParserSaveToFileByHandle(pParser, name);
         Log('videoinfo : '+name);
      end;
      'debug' : begin
         if (bvalid) then
         gProfile.bDbgUnitDebugInfo := bstate
         else
         if (s2='') then
         gProfile.bDbgUnitDebugInfo := not gProfile.bDbgUnitDebugInfo;
      end;
      'fps' : begin
         if (bvalid) then
         gProfile.bShowFPS := bstate
         else
         if (s2='') then
         gProfile.bShowFPS := not gProfile.bShowFPS;
      end;
      'cheat' : begin
         gProfile.bNoAchs := True;
         case s2 of
            'showallmissions' : gProfile.bCheatShowAllMissions := True;
            'place' : gProfile.bCheatPlaceUnits := True;
            'fog' : gProfile.bCheatNoFogOfWar := True;
            'switch' : gProfile.bCheatSwitchPlayer := True;
            'peace' : gProfile.bCheatPeaceMode := True;
            'speed' : gProfile.bCheatNoSpeedLimit := True;
            'copy' : gProfile.bCheatCopyPaste := True;
            'move' : gProfile.bCheatMoveUnits := True;
         end;
      end;
      'perf': begin
         var atotal, arender, aprogress, alansend, alanrecv: Boolean;
         GetPerformanceView(atotal, arender, aprogress, alansend, alanrecv);
         if s2='' then
         SetPerformanceView(not atotal, not atotal, not atotal, not atotal, not atotal)
         else begin
            case s2 of
               'true' , '1': SetPerformanceView(true, true, true, true, true);
               'false', '0': SetPerformanceView(false, false, false, false, false);
               'total'   : begin if s3='' then SetPerformanceView(not atotal, arender, aprogress, alansend, alanrecv) else SetPerformanceView(StrToBool(s3), arender, aprogress, alansend, alanrecv); end;
               'render'  : begin if s3='' then SetPerformanceView(atotal, not arender, aprogress, alansend, alanrecv) else SetPerformanceView(atotal, StrToBool(s3), aprogress, alansend, alanrecv); end;
               'progress': begin if s3='' then SetPerformanceView(atotal, arender, not aprogress, alansend, alanrecv) else SetPerformanceView(atotal, arender, StrToBool(s3), alansend, alanrecv); end;
               'lansend' : begin if s3='' then SetPerformanceView(atotal, arender, aprogress, not alansend, alanrecv) else SetPerformanceView(atotal, arender, aprogress, StrToBool(s3), alanrecv); end;
               'lanrecv' : begin if s3='' then SetPerformanceView(atotal, arender, aprogress, alansend, not alanrecv) else SetPerformanceView(atotal, arender, aprogress, alansend, StrToBool(s3)); end;
            end;
         end;
      end;
      'record': begin
         if bvalid then
         _net_setRecord(bstate)
         else if s2='' then
         _net_setRecord(not _net_getRecord)
         else if s2='clean' then
         CleanRecordManager;
      end;
      'saverep', 'savereplay': begin
         var filename: String=s2;
         if filename='' then
         filename:=ExtractFileName(GetCurrentMapFileName);
         if filename<>'' then begin
            if gScenario.bexists then
            UserProfileSaveReplay(filename, GetCurrentMapFileName)
            else begin
               _map_RestoreSettings;
               UserProfileSaveReplay(filename, gMap.name);
            end;
         end;
      end;
      'saverepwithmap': begin
         var filename: String=s2;
         if filename='' then
         filename:=ExtractFileName(GetCurrentMapFileName);
         if filename<>'' then
         UserProfileSaveReplay(filename, GetCurrentMapFileName);
      end;
      'loadrep', 'loadreplay': begin
         var filename: String=ExtractFileName(s2);
         if(filename='') and (GetCurrentMapFileName<>'') then
         filename:=ExtractFileName(GetCurrentMapFileName);
         if filename<>'' then
         UserProfileLoadReplay(filename);
      end;
      'close', 'exit': CloseQuery;
      'restart': RestartApplication;
   end;
end;

// _misc_ProcessNetMessage
//
procedure _misc_ProcessNetMessage(var str : String);
begin
   const cSpaceChar  = 32; // space
   var s1, s2, s3, s4 : String;
   if IsDelimiterCharExists(str, cSpaceChar) then
   _misc_GetDelimiterString(str, cSpaceChar, s1, s2, s3, s4)
   else
   s1 := str;

   var strpart : String;
   if s1='net' then
   begin
      strpart := SubStr(str, 9, StrLength(str)-8);
      s1 := s2;
      s2 := s3;
      s3 := s4;
   end
   else
   strpart := SubStr(str, 5, StrLength(str)-4);
   var bvalid, bstate : Boolean;
   _misc_CheckStringValidBool(s2, bvalid, bstate);
   case s1 of
      'log' : Log(strpart);
      'msg' : _misc_ProcessMessage(strpart);
      'progressthread' : begin
         if (bvalid) then
         SetProgressPlayersThread(bstate)
         else
         SetProgressPlayersThread(not GetProgressPlayersThread);
         Log('SetProgressPlayersThread('+BoolToStr(GetProgressPlayersThread)+');');
      end;
      'help' : begin
         Log('net %cmd% %value% ENTER; cmd examples: log, msg, progressthread; value examples true false "custom text"');
      end;
   end;
end;

// _misc_UpdateClientList
//
procedure _misc_UpdateClientList(bUpdate, bGUIUpdate, bIfRoom, bIfShell : Boolean);
begin
   if (bUpdate) then
   begin
      var pClients : Integer = _parser_ParserTemporary(True);
      LanPublicServerClientsToParser(pClients);
      gInternetShell.clients.Clear;
      var i : Integer;
      for i:=0 to ParserGetCountByHandle(pClients)-1 do
      begin
         var pClient : Integer = ParserSelectByHandleByIndex(pClients, i);
         if (pClient<>0) then
         gInternetShell.clients.AddOrUpdateFromParser(pClient);
      end;
   end;
   if (bGUIUpdate) then
   begin
      if (gInternetShell.bshowroom) then
      begin
         if (bIfRoom) then
         gbool_gui_showcustomgame := True; //ExecuteState('ShowCustomGame')
      end
      else
      if (bIfShell) then
      gbool_gui_showinternetshell := True; //ExecuteState('ShowInternetShell');
   end;
end;

// _misc_UpdateSessionList
//
procedure _misc_UpdateSessionList(bUpdate, bGUIUpdate, bIfRoom, bIfShell : Boolean);
begin
   if (bUpdate) then
   begin
      var pSessions : Integer = _parser_ParserTemporary(True);
      LanPublicServerSessionsToParser(pSessions);
      gInternetShell.sessions.Clear;
      var i : Integer;
      for i:=0 to ParserGetCountByHandle(pSessions)-1 do
      begin
         var pSession : Integer = ParserSelectByHandleByIndex(pSessions, i);
         if (pSession<>0) then
         gInternetShell.sessions.AddFromParser(pSession);
      end;
   end;
   if (bGUIUpdate) then
   begin
      if (gInternetShell.bshowroom) then
      begin
         if (bIfRoom) then
         gbool_gui_showcustomgame := True; //ExecuteState('ShowCustomGame')
      end
      else
      if (bIfShell) then
      gbool_gui_showinternetshell := True; //ExecuteState('ShowInternetShell');
   end;
end;

// _misc_SetCampaignAch
//
function _misc_SetCampaignAch(var achsid : String) : Boolean;
begin
   Result := False;
   var achid : Integer;
   var achkeyid : Integer = -1;
   if IsDelimiterCharExists(achsid, gc_gui_delimiterchar) then
   begin
      if (GetDelimiterStringCount(achsid, gc_gui_delimiterchar, gc_gui_quotechar)=2) then
      begin
         var achsidPart : String = GetDelimiterStringByIndex(achsid, gc_gui_delimiterchar, gc_gui_quotechar, 0);
         var achkeyidPart : String = GetDelimiterStringByIndex(achsid, gc_gui_delimiterchar, gc_gui_quotechar, 1);
         achid := StrToInt(achsidPart);
         achkeyid := StrToInt(achkeyidPart);
      end
      else
      ErrorLog('Ach : invalid string = '+achsid);
   end
   else
   achid := StrToInt(achsid);
   if (achid>0) and (achid<gc_ach_existscount) then
   begin
      if (achkeyid>=0) then
      begin
         if (gProfileUserStruct.achs.ach[achid].bbitcoded) and (achkeyid>=0) and (achkeyid<32) then
         begin
            Log('AchProgress = '+achsid);
            gProfileUserStruct.achs.ach[achid].cur := gProfileUserStruct.achs.ach[achid].cur or (1 shl achkeyid);
            Result := True;
         end
         else
         ErrorLog('Ach is not bitcoded but or invalid achkeyid input data is. = '+achsid);
      end
      else
      if (not gProfileUserStruct.achs.ach[achid].bbitcoded) then
      begin
         Log('Ach='+IntToStr(achid));
         gProfileUserStruct.achs.ach[achid].cur := gProfileUserStruct.achs.ach[achid].cur+1;
         Result := True;
      end
      else
      ErrorLog('Ach is bitcoded but got invalid achkeyid input data is. = '+achsid);
   end
   else
   ErrorLog('Ach state got wrong input value! = '+achsid);
end;

procedure _misc_ConnectIPIndexIncrease();
begin
   Log('IPIndexIncrease = '+IntToStr(gConnectIPIndex));
   if (gConnectIPCount>0) then
   gConnectIPIndex := (gConnectIPIndex+1) mod gConnectIPCount
   else
   gConnectIPIndex := 0;
end;

procedure _misc_ConnectIPIndexDecrease();
begin
   Log('IPIndexDecrease = '+IntToStr(gConnectIPIndex));
   if (gConnectIPCount>0) then
   gConnectIPIndex := (gConnectIPIndex-1) mod gConnectIPCount
   else
   gConnectIPIndex := 0;
   if (gConnectIPIndex<0) then
   gConnectIPIndex := gConnectIPCount-1;
end;

// _misc_LanServerSendResults
//
function _misc_LanServerSendResults() : Boolean;
begin
   Result := False;
   Log('_misc_LanServerSendResults _net_IsServer = '+BoolToStr(_net_IsServer));
   if (_net_IsServer) and ((gInterface.gamemode=gc_gamemode_game) or (gInterface.gamemode=gc_gamemode_spectator)) then
   begin
      var pParser : Integer = _parser_ParserTemporary(True);
      var i : Integer;
      for i:=0 to gc_MaxPlayerCount-1 do
      //if {(gMap.players[i].bhuman) and (gMap.players[i].lanid>0)} then
      begin
         var pLanResult : Integer = ParserAddChildByIndex(pParser, '*');
         if (pLanResult<>0) then
         begin
            ParserSetIntValueByKeyByHandle(pLanResult, 'id', gMap.players[i].lanid);
            ParserSetIntValueByKeyByHandle(pLanResult, 'ind', i);
            if (gPlayer[i].victorystate=gc_player_victorystate_win) then
            ParserSetIntValueByKeyByHandle(pLanResult, 'res', gc_player_victorystate_win)
            else
            if (gPlayer[i].victorystate=gc_player_victorystate_lose) then
            ParserSetIntValueByKeyByHandle(pLanResult, 'res', gc_player_victorystate_lose)
            else
            ParserSetIntValueByKeyByHandle(pLanResult, 'res', gc_player_victorystate_none);
         end;
      end;
      Log('_misc_LanServerSendResults LanSendParser');
      LanSendParser(gc_LAN_GAME_SESSION_RESULTS, pParser);
   end;
end;

// _misc_GetFirstLeaver
//
function _misc_GetFirstLeaver() : Integer;
begin
   var minind : Integer = -1;
   var mintime : Float = 10000000;
   var i : Integer;
   for i:=0 to gc_MaxPlayerCount-1 do
   if (gPlayer[i].victorystategametime<15*60) and (gPlayer[i].victorystategametime>0) and (gPlayer[i].victorystategametime<mintime) then
   begin
      mintime := gPlayer[i].victorystategametime;
      minind := i;
   end;
   Result := minind;
end;

// _misc_LanCloseSessionSetScores
//
function _misc_LanCloseSessionSetScores() : Boolean;
begin
   log('_misc_LanCloseSessionSetScores : start _net_IsServer = '+BoolToStr(_net_IsServer));
   if (_net_IsServer) and ((gInterface.gamemode=gc_gamemode_game) or (gInterface.gamemode=gc_gamemode_spectator)) then
   begin
      var gt : Float = GetGameTime;
      if (((gMap.brating)) or ((not gMap.brating) and gt>60*10)) then
      begin
         Result := True;
         var firstleaverind : Integer = _misc_GetFirstLeaver();
         var i : Integer;
         for i:=0 to gc_MaxPlayerCount-1 do
         if (gMap.players[i].bhuman) and (gMap.players[i].lanid>0) then
         begin
            log('_misc_LanCloseSessionSetScores : LanSrvSetClientScore : i='+IntToStr(i)+' : lanid='+IntToStr(gMap.players[i].lanid)+' victory='+IntToStr(gPlayer[i].victorystate));
            var w : Integer;
            if gMap.brating then
            w := 1
            else
            w := 2;
            if (gMap.gamestage>gc_map_gamestage_waitingloading) then
            begin
               if (gPlayer[i].victorystate=gc_player_victorystate_win) then
               begin
                  if (firstleaverind=i) then
                  LanSrvSetClientScore(gMap.players[i].lanid, -w)
                  else
                  LanSrvSetClientScore(gMap.players[i].lanid, w);
               end
               else
               if (gPlayer[i].victorystate=gc_player_victorystate_lose) then
               begin
                  if (gMap.brating) then
                  begin
                     case i of
                        0 : begin
                           if (firstleaverind<>1) then
                           LanSrvSetClientScore(gMap.players[i].lanid, -w)
                        end;
                        1 : begin
                           if (firstleaverind<>0) then
                           LanSrvSetClientScore(gMap.players[i].lanid, -w)
                        end;
                        2 : begin
                           if (firstleaverind<>3) then
                           LanSrvSetClientScore(gMap.players[i].lanid, -w)
                        end;
                        3 : begin
                           if (firstleaverind<>2) then
                           LanSrvSetClientScore(gMap.players[i].lanid, -w)
                        end;
                     end;
                  end;
               end;
            end;
         end;
      end;
      log('*** _misc_LanCloseSessionSetScores : LanPublicServerCloseSession GT='+FloatToStr(GetGameTime)+' ***');
      LanPublicServerCloseSession;
   end;
end;

// _misc_CheckEndGame
//
function _misc_CheckEndGame() : Boolean;
begin
   Result := False;
   var i, j : Integer;
   var bSecondTeamExist : Boolean;
   var tmp : Integer = -1;
   for i:=0 to gc_MaxPlayerCount-1 do
   begin
      if (gMap.players[i].bexists) then
      begin
         if (tmp=-1) then
         tmp := gPlayer[i].team
         else
         if (tmp<>gPlayer[i].team) then
         begin
            bSecondTeamExist := True;
            break;
         end;
      end;
   end;
   if (bSecondTeamExist) then
   begin
      var bOneTeamLeft : Boolean = True;
      for [MAIN]i:=0 to gc_MaxPlayerCount-1 do
      for j:=0 to gc_MaxPlayerCount-1 do
      begin
         if (gPlayer[i].bexists) and (not gMap.players[i].bleave) then
         begin
            if (i<>j) and (gPlayer[i].team<>gPlayer[j].team) and ((gPlayer[j].bexists) and (not gMap.players[j].bleave)) then
            begin
               bOneTeamLeft := False;
               break(MAIN);
            end;
         end;
      end;
      if (bOneTeamLeft) then
      begin
         Result := True;
         var bchanges : Boolean;
         var winnerteam : Integer;
         for i:=0 to gc_MaxPlayerCount-1 do
         if (gPlayer[i].bexists) and (not gMap.players[i].bleave) and (gPlayer[i].victorystate<>gc_player_victorystate_win) then
         begin
            gPlayer[i].victorystate := gc_player_victorystate_win;
            if (gPlayer[i].victorystategametime=0) then
            gPlayer[i].victorystategametime := GetGameTime;
            winnerteam := gPlayer[i].team;
            bchanges := True;
         end;

         if (winnerteam>0) then
         for i:=0 to gc_MaxPlayerCount-1 do
         if (gMap.players[i].bhuman) and (gMap.players[i].lanid>0) and (gPlayer[i].victorystate=gc_player_victorystate_lose) and (gPlayer[i].team=winnerteam) then
         begin
            gPlayer[i].victorystate := gc_player_victorystate_win;
            if (gPlayer[i].victorystategametime=0) then
            gPlayer[i].victorystategametime := GetGameTime;
         end;

         for i:=0 to gc_MaxPlayerCount-1 do
         if (gMap.players[i].bhuman) and (gMap.players[i].lanid>0) and (gPlayer[i].victorystate<>gc_player_victorystate_lose) and (gPlayer[i].victorystate<>gc_player_victorystate_win) then
         begin
            gPlayer[i].victorystate := gc_player_victorystate_lose;
            if (gPlayer[i].victorystategametime=0) then
            gPlayer[i].victorystategametime := GetGameTime;
         end;

         if (bchanges) then
         begin
            log('_misc_CheckEndGame : bchanges');
            _misc_LanServerSendResults;
            _misc_LanCloseSessionSetScores;
         end;
      end;
   end;
end;

// _misc_Surrender
//
procedure _misc_Surrender(blanterminate : Boolean);
begin
   var plInd : Integer = GetPlayerIndexInterfaceIO;
   if (not gbool_gui_bspectator) then
   gMap.players[plInd].bleave := True;
   if (_net_IsOnline) then
   begin
      if (_net_IsServer) then
      begin
         if (not gbool_gui_bspectator) then
         gPlayer[plind].victorystate := gc_player_victorystate_lose;
         if (gPlayer[plind].victorystategametime=0) then
         gPlayer[plind].victorystategametime := GetGameTime;
         if (not _misc_CheckEndGame) then // also do some stuff, not just return value
         begin
            if (blanterminate) then
            LanSendParser(gc_LAN_GAME_SERVER_LEAVE, _parser_ParserEmpty);
         end;
      end
      else
      if (_net_IsClient) then
      LanSendParser(gc_LAN_GAME_SURRENDER, _parser_ParserEmpty);

      var leaverind : Integer = _misc_GetFirstLeaver;
      if (LanPublicServerProfCountry<>'') and (leaverind>=0) and (leaverind<>plInd) then
      begin
         var country : String = LanPublicServerProfCountry;
         if (StrExists(country, '|f=')) then
         begin
            var pos : Integer = StrPos('|f=', country);
            var lm : String = SubStr(country, pos+3, 1);
            country := StrReplace(country, '|f='+lm, '');
            LanSetMyCountry(country);
            LanPublicServerRegister;
         end;
      end;
      if (blanterminate) then
      LanTerminateGame;
   end
   else
   if (_net_IsOffline) then
   begin
      gPlayer[plind].victorystate := gc_player_victorystate_lose;
      if (gPlayer[plind].victorystategametime=0) then
      gPlayer[plind].victorystategametime := GetGameTime;
      _misc_CheckEndGame;
   end;
end;

// _misc_GUICancelBuilding
//
procedure _misc_GUICancelBuilding(bCancelAll, bResetCID : Boolean);
begin
   var goHnd : Integer = gint_gui_pickedbldhnd;
   var tag : Integer = Round(GetGameObjectTagFloatByHandle(goHnd));
   var cid : Integer = tag div 256;
   var id : Integer = tag mod 256;
   var usage : Integer = gObjProp[cid][id].usage;
   if (usage=gc_obj_usage_hardwall) or (usage=gc_obj_usage_weakwall) then
   begin
      if (bCancelAll) then
      begin
         gWallCluster.SetWallBuildMode(-1, -1, -1);
         GameObjectDestroyByHandle(goHnd);
         gint_gui_pickedbldhnd := 0;
         if (bResetCID) then
         gint_gui_pickedbldcid := -1;
      end
      else
      if gWallCluster.cornPt.GetCount>1 then
      begin
         gWallCluster.SetPreviousSegment;
         if (gWallCluster.Cells.GetCount>0) then
         begin
            var mx, my, mz : Float;
            GetCurrentMouseWorldCoord(mx, my, mz);
            gWallCluster.UndoSegment;
            gWallCluster.ConnectToPoint(floor(mx), floor(mz));
            gWallCluster.CreateSprites;
            _misc_UpdateWall(gWallCluster);
         end;
      end
      else
      if gWallCluster.cornPt.GetCount>0 then
      begin
         gWallCluster.SetWallBuildMode(-1, -1, -1);
         gWallCluster.FirstWall := True;
      end
      else
      begin
         gWallCluster.SetWallBuildMode(-1, -1, -1);
         GameObjectDestroyByHandle(goHnd);
         gint_gui_pickedbldhnd := 0;
         if (bResetCID) then
         gint_gui_pickedbldcid := -1;
      end;
   end
   else
   begin
      GameObjectDestroyByHandle(goHnd);
      gint_gui_pickedbldhnd := 0;
      if (bResetCID) then
      gint_gui_pickedbldcid := -1;
   end;
end;

procedure _misc_WriteSyncClient(curuid, nextuid, envcount : Integer);
begin
   var pSyncs : Integer = _parser_ParserTemporary(True);
   ParserSetIntValueByKeyByHandle(pSyncs, 'cuid', curuid);
   ParserSetIntValueByKeyByHandle(pSyncs, 'nuid', nextuid);
   ParserSetIntValueByKeyByHandle(pSyncs, 'envc', envcount);
   //LanSendParser(gc_LAN_GAME_SYNC_REQUEST, pSyncs);
   LanPublicServerSendSessionParser(gLanHostID, gc_LAN_GAME_SYNC_REQUEST, pSyncs);
end;

procedure _misc_WriteSyncServer(requestLanID, cuid, nuid, envc : Integer);
begin
   procedure AddUnitInfoToParser(pSync, syncuid : Integer);
   begin
      var hnd : Integer = GetGameObjectHandleByUniqueId(syncuid);
      var pobj : Pointer = nil;
      if (hnd<>0) then
      pobj := _unit_GetTObj(hnd);
      var bexists : Boolean = (hnd<>0) and (pobj<>nil);
      ParserSetIntValueByKeyByHandle(pSync, 'syncuid', syncuid); // RecordCustomWriteInteger(syncuid);
      ParserSetBoolValueByKeyByHandle(pSync, 'bexists', bexists); // RecordCustomWriteBoolean(bexists);
      if (bexists) then
      begin
         var racename : String = GetGameObjectRaceNameByHandle(hnd);
         var basename : String = GetGameObjectBaseNameByHandle(hnd);
         var posx : Float = GetGameObjectPositionXByHandle(hnd);
         var posz : Float = GetGameObjectPositionZByHandle(hnd);
         var scale : Float = GetGameObjectScaleXByHandle(hnd);
         var upx, upy, upz, dirx, diry, dirz : Float;
         GetGameObjectOrientationByHandle(hnd, upx, upy, upz, dirx, diry, dirz);
         //var angle : Float = _misc_GetDirAngleToXVector(upx, upz);
         var statestag : Integer = GetGameObjectStatesTagByHandle(hnd);
         var stohnd : Integer = GetGameObjectSTOHandleByHandle(hnd);
         var sto : Integer = GetGameObjectUniqueIdByHandle(GetGameObjectSTOHandleByHandle(hnd));
         var stpx : Float = GetGameObjectStateTargetPositionXByHandle(hnd);
         var stpz : Float = GetGameObjectStateTargetPositionXByHandle(hnd);
         var sta : Float = GetGameObjectSTArrowAngleByHandle(hnd);

         var cid : Integer = TObj(pobj).cid;
         var id : Integer = TObj(pobj).id;
         var pl : Integer = TObj(pobj).pl;
         var hp : Integer = TObj(pobj).hp;
         var bbuilt : Boolean = TObj(pobj).bbuilt;
         var bdead : Boolean = TObj(pobj).bdead;
         var buildprogress : Float = TObj(pobj).buildprogress;
         var uniqrnd : Float = TObj(pobj).uniqrnd;

         ParserSetValueByKeyByHandle(pSync, 'racename', racename); // RecordCustomWriteString(racename);
         ParserSetValueByKeyByHandle(pSync, 'basename', basename); // RecordCustomWriteString(basename);
         ParserSetFloatValueByKeyByHandle(pSync, 'posx', posx); // RecordCustomWriteFloat(posx);
         ParserSetFloatValueByKeyByHandle(pSync, 'posz', posz); // RecordCustomWriteFloat(posz);
         ParserSetFloatValueByKeyByHandle(pSync, 'scale', scale); // RecordCustomWriteFloat(scale);
         //ParserSetFloatValueByKeyByHandle(pSync, 'angle', angle); // //RecordCustomWriteFloat(angle);
         ParserSetFloatValueByKeyByHandle(pSync, 'upx', upx); // RecordCustomWriteFloat(upx);
         ParserSetFloatValueByKeyByHandle(pSync, 'upy', upy); // RecordCustomWriteFloat(upy);
         ParserSetFloatValueByKeyByHandle(pSync, 'upz', upz); // RecordCustomWriteFloat(upz);
         ParserSetFloatValueByKeyByHandle(pSync, 'dirx', dirx); // RecordCustomWriteFloat(dirx);
         ParserSetFloatValueByKeyByHandle(pSync, 'diry', diry); // RecordCustomWriteFloat(diry);
         ParserSetFloatValueByKeyByHandle(pSync, 'dirz', dirz); // RecordCustomWriteFloat(dirz);
         ParserSetIntValueByKeyByHandle(pSync, 'statestag', statestag); // RecordCustomWriteInteger(statestag);
         ParserSetIntValueByKeyByHandle(pSync, 'sto', sto); // RecordCustomWriteInteger(sto);
         ParserSetFloatValueByKeyByHandle(pSync, 'stpx', stpx); // RecordCustomWriteFloat(stpx);
         ParserSetFloatValueByKeyByHandle(pSync, 'stpz', stpz); // RecordCustomWriteFloat(stpz);
         ParserSetFloatValueByKeyByHandle(pSync, 'sta', sta); // RecordCustomWriteFloat(sta);
         // TObj
         ParserSetIntValueByKeyByHandle(pSync, 'cid', cid); // RecordCustomWriteInteger(cid);
         ParserSetIntValueByKeyByHandle(pSync, 'id', id); // RecordCustomWriteInteger(id);
         ParserSetIntValueByKeyByHandle(pSync, 'pl', pl); // RecordCustomWriteInteger(pl);
         ParserSetIntValueByKeyByHandle(pSync, 'hp', hp); // RecordCustomWriteInteger(hp);
         ParserSetBoolValueByKeyByHandle(pSync, 'bbuilt', bbuilt); // RecordCustomWriteBoolean(bbuilt);
         ParserSetBoolValueByKeyByHandle(pSync, 'bdead', bdead); // RecordCustomWriteBoolean(bdead);
         ParserSetFloatValueByKeyByHandle(pSync, 'buildprogress', buildprogress); // RecordCustomWriteFloat(buildprogress);
         ParserSetFloatValueByKeyByHandle(pSync, 'uniqrnd', uniqrnd); // RecordCustomWriteFloat(uniqrnd);
      end;
   end;

   Log('WriteSyncServer : cuid = '+IntToStr(cuid)+' nuid = '+IntToStr(nuid)+' envc = '+IntToStr(envc));
   var pSyncs : Integer = _parser_ParserTemporary(True);
   var nextuid : Integer = GetMapNextUniqId;
   var count : Integer = nextuid-nuid;
   //ParserSetIntValueByKeyByHandle(pSyncs, 'count', count); // RecordCustomWriteInteger(count);

   if (cuid<nuid) then
   begin
      var pCurUID : Integer = ParserAddChildByIndex(pSyncs, 'cuid');
      AddUnitInfoToParser(pCurUID, cuid);
   end;

   var pSyncUIDs : Integer = ParserAddChildByIndex(pSyncs, 'nuid');
   var i : Integer;
   for i:=0 to count-1 do
   begin
      var syncuid : Integer = nuid+i;
      var pSync : Integer = ParserAddChildByIndex(pSyncUIDs, '*');
      AddUnitInfoToParser(pSync, syncuid);
   end;

   var envplhnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
   var myenvcount : Integer = GetPlayerGameObjectsCountByHandle(envplhnd);
   if (envc>myenvcount) then
   begin
      var pRemovedEnvs : Integer = ParserAddChildByIndex(pSyncs, 'envc');
      ParserSelectByHandle(pRemovedEnvs);
      var i, uid, hnd : Integer;
      for uid:=1 to gMap.lastenvuid do
      begin
         hnd := GetGameObjectHandleByUniqueId(uid);
         if (hnd=0) then
         begin
            ParserSetIntValueByIndex(i, uid);
            i := i+1;
         end;
      end;
      if (i>0) then
      Log('_misc_WriteSyncServer : envcount<>myencount : difference in units count = '+IntToStr(i));
   end;
   LanPublicServerSendSessionParser(requestLanID, gc_LAN_GAME_SYNC_DATA, pSyncs);
end;

procedure _misc_ReadSyncServer(requestLanID, pLan : Integer);
begin
   var cuid : Integer = ParserGetIntValueByKeyByHandle(pLan, 'cuid');
   var nuid : Integer = ParserGetIntValueByKeyByHandle(pLan, 'nuid');
   var envc : Integer = ParserGetIntValueByKeyByHandle(pLan, 'envc');
   _misc_WriteSyncServer(requestLanID, cuid, nuid, envc);
   Log('ReadSyncServer : sent WriteSyncServer nuid = '+IntToStr(nuid));
end;

procedure _misc_ReadSyncClient(pLan : Integer);
begin
   procedure CreateGameObjectFromParser(pSync : Integer);
   begin
      var syncuid : Integer = ParserGetIntValueByKeyByHandle(pSync, 'syncuid'); // RecordCustomReadInteger;
      var bexists : Boolean = ParserGetBoolValueByKeyByHandle(pSync, 'bexists'); // RecordCustomReadBoolean;
      if (bexists) then
      begin
         var clientHnd : Integer = GetGameObjectHandleByUniqueId(syncuid);
         if (clientHnd=0) then
         begin
            Log('ReadSyncClient : restore uid = '+IntToStr(syncuid));
            var racename : String = ParserGetValueByKeyByHandle(pSync, 'racename'); // RecordCustomReadString;
            var basename : String = ParserGetValueByKeyByHandle(pSync, 'basename'); // RecordCustomReadString;
            var posx : Float = ParserGetFloatValueByKeyByHandle(pSync, 'posx'); // RecordCustomReadFloat;
            var posz : Float = ParserGetFloatValueByKeyByHandle(pSync, 'posz'); // RecordCustomReadFloat;
            var scale : Float = ParserGetFloatValueByKeyByHandle(pSync, 'scale'); // RecordCustomReadFloat;
            //var angle : Float = ParserGetFloatValueByKeyByHandle(pSync, 'angle'); // RecordCustomReadFloat;
            var upx : Float = ParserGetFloatValueByKeyByHandle(pSync, 'upx'); // RecordCustomReadFloat;
            var upy : Float = ParserGetFloatValueByKeyByHandle(pSync, 'upy'); // RecordCustomReadFloat;
            var upz : Float = ParserGetFloatValueByKeyByHandle(pSync, 'upz'); // RecordCustomReadFloat;
            var dirx : Float = ParserGetFloatValueByKeyByHandle(pSync, 'dirx'); // RecordCustomReadFloat;
            var diry : Float = ParserGetFloatValueByKeyByHandle(pSync, 'diry'); // RecordCustomReadFloat;
            var dirz : Float = ParserGetFloatValueByKeyByHandle(pSync, 'dirz'); // RecordCustomReadFloat;
            var statestag : Integer = ParserGetIntValueByKeyByHandle(pSync, 'statestag'); // RecordCustomReadInteger;
            var sto : Integer = ParserGetIntValueByKeyByHandle(pSync, 'sto'); // RecordCustomReadInteger;
            var stpx : Float = ParserGetFloatValueByKeyByHandle(pSync, 'stpx'); // RecordCustomReadFloat;
            var stpz : Float = ParserGetFloatValueByKeyByHandle(pSync, 'stpz'); // RecordCustomReadFloat;
            var sta : Float = ParserGetFloatValueByKeyByHandle(pSync, 'sta'); // RecordCustomReadFloat;
            // TObj
            var cid : Integer = ParserGetIntValueByKeyByHandle(pSync, 'cid'); // RecordCustomReadInteger;
            var id : Integer = ParserGetIntValueByKeyByHandle(pSync, 'id'); // RecordCustomReadInteger;
            var pl : Integer = ParserGetIntValueByKeyByHandle(pSync, 'pl'); // RecordCustomReadInteger;
            var hp : Integer = ParserGetIntValueByKeyByHandle(pSync, 'hp'); // RecordCustomReadInteger;
            var bbuilt : Boolean = ParserGetBoolValueByKeyByHandle(pSync, 'bbuilt'); // RecordCustomReadBoolean;
            var bdead : Boolean = ParserGetBoolValueByKeyByHandle(pSync, 'bdead'); // RecordCustomReadBoolean;
            var buildprogress : Float = ParserGetFloatValueByKeyByHandle(pSync, 'buildprogress'); // RecordCustomReadFloat;
            var uniqrnd : Float = ParserGetFloatValueByKeyByHandle(pSync, 'uniqrnd'); // RecordCustomReadFloat;

            var plHnd : Integer = GetPlayerHandleByIndex(pl);
            gint_unit_parentcid := cid;
            var nextuid : Integer = GetMapNextUniqId;
            if (nextuid=syncuid) then
            begin
               if (not bbuilt) and (gObjProp[cid][id].media=gc_obj_media_water) then
               gbool_unit_isshipdummy := True;
               var hnd : Integer = CreatePlayerGameObjectHandleByHandle(plHnd, racename, basename, posx, 0, posz);
               var newuid : Integer = GetGameObjectUniqueIdByHandle(hnd);
               if (newuid<>syncuid) then
               begin
                  //GameObjectChangeUniqIdByHandle(hnd, syncuid);
                  ErrorLog('ReadSyncClient, created gameobject have other UID then needed. My = '+IntToStr(newuid)+' syncuid = '+IntToStr(syncuid));
               end;
               var pobj: Pointer = _unit_GetTObj(hnd);
               if (pobj<>nil) then
               begin
                  // check valid start
                  if (cid<>TObj(pobj).cid) or (id<>TObj(pobj).id) or (pl<>TObj(pobj).pl) then
                  ErrorLog('ReadSyncClient : invalid unit');
                  // check valid finish
                  TObj(pobj).hp := hp;
                  if (gObjProp[cid][id].bbuilding) then
                  begin
                     TObj(pobj).bbuilt := False;
                     TObj(pobj).buildprogress := 0;
                     _unit_ControlBuildProgress(hnd);
                  end;
                  TObj(pobj).bbuilt := bbuilt;
                  TObj(pobj).bdead := bdead;
                  TObj(pobj).uniqrnd := uniqrnd;
                  TObj(pobj).buildprogress := buildprogress;
               end
               else
               _net_ErrorLog('ReadSyncClient invalid pointer: pobj=nil');
               SetGameObjectOrientationByHandle(hnd, upx, upy, upz, dirx, diry, dirz);
               SetGameObjectScaleByHandle(hnd, scale, scale, scale);
               GameObjectDoRayCastTerrainByHandle(hnd);
               SetGameObjectStatesTagByHandle(hnd, statestag);
               SetGameObjectStatesTagForceUpdateByHandle(hnd);
               SetGameObjectSTOHandleByHandle(hnd, sto);
               SetGameObjectStateTargetPositionByHandle(hnd, stpx, 0, stpz);
               SetGameObjectSTArrowAngleByHandle(hnd, sta);
            end
            else
            ErrorLog('ReadSyncClient : nextuid<>syncuid');
         end;
      end;
   end;

   //var count : Integer = ParserGetIntValueByKeyByHandle(pLan, 'count');
   var i : Integer;
   var pRemovedEnvs : Integer = ParserSelectByHandleByKey(pLan, 'envc');
   if (pRemovedEnvs<>0) then
   begin
      var count : Integer = ParserGetCountByHandle(pRemovedEnvs);
      for i:=0 to count-1 do
      begin
         var removeduid : Integer = ParserGetIntValueByIndexByHandle(pRemovedEnvs, i);
         var hnd : Integer = GetGameObjectHandleByUniqueId(removeduid);
         if (hnd<>0) then
         GameObjectRequestToDestroyByHandle(hnd);
      end;
   end;

   var pSyncUIDs : Integer = ParserSelectByHandleByKey(pLan, 'nuid');
   if (pSyncUIDs<>0) then
   begin
      var count : Integer = ParserGetCountByHandle(pSyncUIDs);
      for i:=0 to count-1 do
      begin
         var pSync : Integer = ParserSelectByHandleByIndex(pSyncUIDs, i);
         CreateGameObjectFromParser(pSync);
      end;
   end;

   var pCurUID   : Integer = ParserSelectByHandleByKey(pLan, 'cuid');
   if (pCurUID<>0) then
   ErrorLog('unsynch uid is less then MapNextUID-1. This unit was deleted on client but exists on server');
end;

// _unit_DestroyObj
//
procedure _unit_DestroyObj(const hnd: Integer);
begin
   // Выполняется при уничтожении юнита
   _misc_UnitRemoveMiniMapPrimitive(hnd);

   BehaviourClear(hnd);

   //if GetGameObjectPickedByHandle(hnd) then // done in _unit_RemoveObjFromPlayerCounters
   //   gSelectedObjects.Remove(hnd);

   var pobj: Pointer=_unit_GetTObj(hnd);
   if (pobj<>nil) then
   begin
      if (TObj(pobj).insideofuid<>0) then
      _unit_RemoveFromInside(hnd);

      // удаление юнита из счетчиков игрока
      _unit_RemoveObjFromPlayerCounters(hnd, TObj(pobj).bbuilt, False);

      TObj(pobj).bdead:=True;

      if (_misc_IsProcessAI) then
      _unit_SetSTO(hnd, 0)
      else
      _unit_SetClientSTO(hnd, 0);

      var pobjprop: Pointer=gObjProp[TObj(pobj).cid][TObj(pobj).id];
      if (pobjprop <> nil) and ((TObjProp(pobjprop).transport>0) or (TObjProp(pobjprop).peasantabsorber>0)) then
      begin
         var pinside: Pointer = _misc_GetObjectArgData(hnd, gc_argunit_inside);
         if pinside<>nil then begin
            gIntegerList.Clear;
            var i : Integer;
            for i:=0 to TIntegerList(pinside).GetCount-1 do
            gIntegerList.Add(TIntegerList(pinside).Get(i));
            _unit_DoUnitsGoOutside(gIntegerList, true, true);
         end;
      end;

      if (TObj(pobj).idlegridx>-1) and (TObj(pobj).idlegridy>-1) then
      _unit_IdleGridRemoveUnit(hnd);

      _unit_PathListRemove(hnd);
      _misc_FixArgLists(hnd);
      _unit_FullClearOrders(hnd);
      _unit_SelectedGroupRemoveUnit(hnd);

      // Show log if player InterfaceIO
      if (TObjProp(pobjprop).bbuilding) and (GetGameObjectPlayerHandleByHandle(hnd)=GetPlayerHandleInterfaceIO) and (not gbool_gui_gatefinished) then
      begin
         var s1, s2 : String;
         var sid : String;
         if (gObjProp[TObj(pobj).cid][TObj(pobj).id].bwall) then
         _unit_ConvertObjIDToSID(TObj(pobj).cid, TObj(pobj).id, sid)
         else
         sid := GetGameObjectBaseNameByHandle(hnd);
         _misc_GetUnitNameBySID(sid, s1);
         _misc_ShowLogMessage(gc_logmessagesid_buildingdestroyed, s1, s2, True);
      end;
      if (TObj(pobj).scangridx>-1) then // DEBUG
      ErrorLog('ACHTUNG _unit_DestroyObj bdead=true but in scangrid');
   end;
end;

// _misc_IsRoomChecksumCompatible
//
function _misc_RoomChecksumCompatible(roomchecksum : String) : Integer;
begin
   Result := gc_result_roomchecksum_uncompatible;
   if (StrLength(roomchecksum)=4) then
   begin
      if (roomchecksum=gstring_checksum) then
      Result := gc_result_roomchecksum_modok
      else
      Result := gc_result_roomchecksum_modneedsync;
   end
   else
   if (StrLength(roomchecksum)=5) then
   begin
      if (not gbool_modsscript) then
      begin
         Result := gc_result_roomchecksum_modok;
         if SubStr(roomchecksum, 2, 4)<>gstring_checksum then
         begin
            ErrorLog('both users without mods but checksum is not equal');
            Result := gc_result_roomchecksum_nomoddifchecksum;
         end;
      end
      else
      if SubStr(roomchecksum, 2, 4)<>gstring_checksum then
      Result := gc_result_roomchecksum_modneedsync
      else
      Result := gc_result_roomchecksum_modok;
   end;
end;

// _misc_SyncUnitsParams
//
procedure _misc_SyncUnitsParams();
begin
   if (_net_IsOnline or _net_IsRecord) then begin
      if (gLanSyncUnitsParamsUIDList.GetCount>0) then
      begin
         var plHnd : Integer;
         var i : Integer;
         for i:=gLanSyncUnitsParamsUIDList.GetCount-1 downto 0 do // delete invalid uids (strange situation)
         begin
            var goUID : Integer = gLanSyncUnitsParamsUIDList.Get(i);
            if (goUID<>0) then
            begin
               var goHnd : Integer = GetGameObjectHandleByUniqueId(goUID);
               if (goHnd<>0) then
               begin
                  if (plHnd=0) then
                  plHnd := GetGameObjectPlayerHandleByHandle(goHnd);
               end
               else
               gLanSyncUnitsParamsUIDList.Delete(i);
            end
            else
            gLanSyncUnitsParamsUIDList.Delete(i);
         end;
         var pArg: Pointer = StateMachineGetArgDataByInd(GetPlayerStateMachineHandle(plHnd), gc_argplayer_plr);
         if pArg<>nil then begin
            TPlayerArgs(pArg).fintlst := gLanSyncUnitsParamsUIDList;
            PlayerExecuteStateByHandle(plHnd, 'WriteSyncUnitsParams');
         end;
         gLanSyncUnitsParamsUIDList.Clear;
      end;
   end
   else
   if (gLanSyncUnitsParamsUIDList.GetCount>0) then
   gLanSyncUnitsParamsUIDList.Clear;
end;

// _misc_IsValidHandle
//
function _misc_IsValidHandle(hnd : Integer) : Boolean;
begin
   Result := (hnd<>0) and (FindUniqIdByGameObject(hnd)>0);
   if (hnd<>0) then
   begin
      if (not Result) then
      Log('not IsValidHandle hnd='+IntToStr(hnd))
      else
      begin
         var baseid : Integer = _misc_GetBaseObjID(hnd);
         if (baseid=gc_baseid_obj) then
         begin
            var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
            if (pobj<>nil) then
            begin
               if (TObj(pobj).bdead) or (TObj(pobj).hp<=0) then
               Result := False
               else
               if (GetGameObjectStatesTagByHandle(hnd) and gc_statetag_essential_death)<>0 then
               Result := False;
            end
            else
            Result := False;
         end
         else
         if (baseid=gc_baseid_res) then
         begin
            var pres : Pointer = _misc_GetObjectArgData(hnd, gc_argenv_res);
            if (pres=nil) then
            begin
               Result := False;
               Log('pres=nil');
            end;
         end;
         if not Result then
         Log('not IsValidHandle second : hnd='+IntToStr(hnd))
      end;
   end;
end;

// _misc_IsValidUID
//
function _misc_IsValidUID(uid : Integer) : Boolean;
begin
   Result := False;
   if (uid<>0) then
   begin
      var hnd : Integer = FindGameObjectByUniqId(uid);
      Result := hnd<>0;
      if (not Result) then
      Log('not IsValidUID = '+IntToStr(uid));
      if (hnd<>0) then
      begin
         var bvalid : Boolean = _misc_IsValidHandle(hnd);
         if (not bvalid) then
         begin
            Result := False;
            Log('IsValidUID, false, uid='+IntToStr(uid));
         end;
      end;
   end;
end;

// _misc_RepairSession
//
procedure _misc_RepairSession(bAI, bMisc, bSTO, bServer : Boolean); // after host changed
begin
   //var bServer : Boolean = _net_IsServer;
   TimeLog('RepairSession start : IsServer='+BoolToStr(bServer)+' _net_IsServer='+BoolToStr(_net_IsServer));

   if (bMisc) and (bServer) then
   _player_FixPlayersCounters; // includes _player_FixPlayerLists;

   var i, j : Integer;
   if (bAI) then
   begin
      for i:=0 to gc_MaxPlayerCount-1 do
      if (gPlayer[i].bai) then
      begin
         gPlayer[i].aiData.Reset;
         _ai_InitInitialLists(i);

         if (not gPlayer[i].bexists) then
         gPlayer[i].bexists := (gPlayer[i].counter.farmused>0);
         _player_UpdateAIData(i, True);
      end;
      _player_FixInvalidHandlesInLists(True);
   end;

   //if (bMisc) then // no more need in this fix
   //_player_FixRallyPos;

   // Clear all lists and variables containing handles to check and recreate them below
   var pl : 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 hnd : Integer = GetGameObjectHandleByIndex(i, plHnd);
         var uid : Integer = FindUniqIdByGameObject(hnd);
         var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            var pstolist : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_stolist);
            var pattlist : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_attlist);
            var pordlist : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_ordlist);
            //var pinside  : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_inside);
            if (pstolist<>nil) then TIntegerList(pstolist).Clear;
            if (pattlist<>nil) then TIntegerList(pattlist).Clear;
            if (pordlist<>nil) then TIntegerList(pordlist).Clear;
            TObj(pobj).trghnd := 0;
            TObj(pobj).prevstouid := 0;
            TObj(pobj).insidereserved := 0;
            TObj(pobj).insideofuid := 0;
            for j:=gc_obj_MaxOrderCount-1 downto 0 do
            begin
               var porderinfo : Pointer = TObj(pobj).orders[j].info;
               TOrderInfo(porderinfo).trg := 0;
            end;
         end;
      end;
   end;

   _res_ResGridClear;
   var envPlHnd : Integer = GetPlayerHandleByIndex(gc_playerind_env);
   if (envPlHnd<>0) then
   begin
      for i:=GetPlayerGameObjectsCountByHandle(envPlHnd)-1 downto 0 do
      begin
         var hnd : Integer = GetGameObjectHandleByIndex(i, envPlHnd);
         var uid : Integer = FindUniqIdByGameObject(hnd);
         if (uid<>0) then
         begin
            var smhnd : Integer = GetGameObjectStateMachineHandle(hnd);
            if (smhnd<>0) then
            begin
               var pres : Pointer = StateMachineGetArgDataByInd(smhnd, gc_argenv_res);
               if (pres<>nil) and (TBaseObj(pres).baseid=gc_baseid_res) then
               begin
                  var pstolist : Pointer = _misc_GetObjectArgData(hnd, gc_argenv_stolist);
                  var pattlist : Pointer = _misc_GetObjectArgData(hnd, gc_argenv_attlist);
                  var pordlist : Pointer = _misc_GetObjectArgData(hnd, gc_argenv_ordlist);
                  if (pstolist<>nil) then TIntegerList(pstolist).Clear;
                  if (pattlist<>nil) then TIntegerList(pattlist).Clear;
                  if (pordlist<>nil) then TIntegerList(pordlist).Clear;

                  if (TRes(pres).itype<>gc_resource_type_none) then
                  _res_ResGridAdd(hnd);
               end;
            end;
         end;
      end;
   end;

   for pl:=0 to gc_MaxPlayerCount-1 do
   begin
      var plHnd : Integer = GetPlayerHandleByIndex(pl);
      for i:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         var hnd : Integer = GetGameObjectHandleByIndex(i, plHnd);
         var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            var statetag : Integer = GetGameObjectStatesTagByHandle(hnd);
            if (TObj(pobj).bdead) or (statetag and gc_statetag_essential_death<>0) then
            begin
               _unit_ClearOrders(hnd);
               SetGameObjectSTOHandleByHandle(hnd, 0);
            end
            else
            begin
               for j:=gc_obj_MaxOrderCount-1 downto 0 do
               begin
                  var porderinfo : Pointer = TObj(pobj).orders[j].info;
                  if (j=0) and ((TOrderInfo(porderinfo).trg<>0) and (_misc_IsValidHandle(TOrderInfo(porderinfo).trg))) and (TObj(pobj).orders[j].itype<>gc_obj_order_type_none) then
                  _misc_IncreaseResGridWorkerCount(hnd, TOrderInfo(porderinfo).trg);

                  case TObj(pobj).orders[j].itype of
                     gc_obj_order_type_produce : begin
                        if (TOrderInfo(porderinfo).amount<>0) then
                        begin
                        end
                        else
                        _unit_ClearOrder(hnd, j, True, False);
                     end;
                     gc_obj_order_type_performupgrade : begin
                     end;
                     gc_obj_order_type_gotomine, gc_obj_order_type_gototransport : begin
                        if (j>0) then
                        _unit_ClearOrder(hnd, j, False, False)
                        else
                        if (TObj(pobj).orders[j].info.trg=0) then // was invalid above
                        _unit_ClearOrder(hnd, j, False, False);
                     end;
                     {gc_obj_order_type_move, }gc_obj_order_type_patrol,
                     {gc_obj_order_type_leavetransport, gc_obj_order_type_leavebuilding,} gc_obj_order_type_exitunits : begin
                        if (j>0) then
                        _unit_ClearOrder(hnd, j, False, False);
                     end;
                     else
                     _unit_ClearOrder(hnd, j, False, False);
                  end;
               end;

               if (bSTO) then
               begin
                  var sto : Integer = GetGameObjectSTOHandleByHandle(hnd);
                  if (sto<>0) and (not _misc_IsValidHandle(sto)) then
                  sto := 0;

                  SetGameObjectSTOHandleByHandle(hnd, 0); // reset stohandle and set it below if needed

                  if (statetag and gc_statetag_essential_none<>0) and (statetag and gc_statetag_visual_none<>0) then
                  begin
                     var pobjprop : Pointer = gObjProp[TObj(pobj).cid][TObj(pobj).id];
                     var usage : Integer = TObjProp(pobjprop).usage;
                     if (bServer) and (not GetGameObjectVisibleByHandle(hnd)) then
                     begin
                        Log('RepairSession : vis='+BoolToStr(GetGameObjectVisibleByHandle(hnd))+' ess_none and vis_none');
                        _unit_SetTagStates(hnd, gc_statetag_visual_hide or gc_statetag_essential_birth);
                     end;
                     if (TObj(pobj).bbuilt) then
                     begin
                        // i am built unit
                        if (sto<>0) then
                        begin
                           var stobaseid : Integer = _misc_GetBaseObjID(sto);
                           // i am peasant
                           if (usage=gc_obj_usage_peasant) then
                           begin
                              if (stobaseid=gc_baseid_obj) then
                              begin
                                 var pobjsto : Pointer = _unit_GetTObj(sto);
                                 if (pobjsto<>nil) then
                                 begin
                                    if TObj(pobjsto).bdead then
                                    _unit_ClearOrders(hnd);

                                    var pobjbasesto : Pointer = _unit_GetObjBase(sto);
                                    var pobjpropsto : Pointer = gObjProp[TObj(pobjsto).cid][TObj(pobjsto).id];
                                    if (TObj(pobjsto).pl=TObj(pobj).pl) then
                                    begin
                                       // target is friend
                                       if (TObjProp(pobjpropsto).bbuilding) then
                                       begin
                                          // target is building
                                          if (not TObj(pobjsto).bbuilt) then
                                          begin
                                             // building is incomplete
                                             gRepairSessionList.Clear;
                                             gRepairSessionList.Add(hnd);
                                             _player_OrderUnitsToBuild(gRepairSessionList, sto, True, False, True);
                                          end
                                          else
                                          if (TObj(pobjsto).hp<TObjBase(pobjbasesto).maxhp) then
                                          begin
                                             // target building need repair
                                             gRepairSessionList.Clear;
                                             gRepairSessionList.Add(hnd);
                                             _player_OrderUnitsToBuild(gRepairSessionList, sto, True, True, True);
                                          end
                                          else
                                          begin
                                             // target is full hp
                                             var stousage : Integer = _unit_GetUsage(sto);
                                             if (stousage=gc_obj_usage_storage) or (stousage=gc_obj_usage_center) or (stousage=gc_obj_usage_mill) then
                                             begin
                                                // target is storehouse
                                                var restype : Integer;
                                                if (statetag and gc_statetag_resource_stone<>0) then
                                                restype := gc_resource_type_stone
                                                else
                                                if (statetag and gc_statetag_resource_wood<>0) then
                                                restype := gc_resource_type_wood
                                                else
                                                if (statetag and gc_statetag_resource_food<>0) then
                                                restype := gc_resource_type_food;
                                                if (restype<>0){(TObj(pobj).restype<>gc_resource_type_none)} then
                                                begin
                                                   // enough resources to return, search for same resource to extract, to force returntostorehouse logic
                                                   var res : Integer = _unit_SearchResourceInRadius(hnd, GetGameObjectPositionXByHandle(hnd), GetGameObjectPositionZByHandle(hnd), gc_obj_res_searchradius*3, gc_obj_res_searchwaitrnd, restype, True);
                                                   if (res<>0) then
                                                   _unit_OrderGainResource(hnd, res, GetGameObjectPositionXByHandle(res), GetGameObjectPositionZByHandle(res));
                                                end;
                                             end
                                             else
                                             if (stousage=gc_obj_usage_mine) then
                                             begin
                                                _unit_OrderGoInside(hnd, sto);
                                             end;
                                          end;
                                       end;
                                    end
                                    else
                                    begin
                                       // target is enemy
                                       _unit_OrderAttack(hnd, sto, False, True, False);
                                    end;
                                 end;
                              end
                              else
                              if (stobaseid=gc_baseid_res) then
                              begin
                                 _unit_OrderGainResource(hnd, sto, GetGameObjectPositionXByHandle(sto), GetGameObjectPositionZByHandle(sto));
                              end;
                           end
                           else
                           if (not TObjProp(pobjprop).bbuilding) then
                           begin
                              // i am not a peasant unit
                              if (stobaseid=gc_baseid_obj) then
                              begin
                                 // target is valid unit
                                 var pobjsto : Pointer = _unit_GetTObj(sto);
                                 var pobjbasesto : Pointer = _unit_GetObjBase(sto);
                                 var pobjpropsto : Pointer = gObjProp[TObj(pobjsto).cid][TObj(pobjsto).id];
                                 if (TObj(pobjsto).pl<>TObj(pobj).pl) then
                                 begin
                                    // target is enemy
                                    _unit_OrderAttack(hnd, sto, False, True, False);
                                 end;
                              end;
                           end
                           else
                           begin
                              // TODO
                              //_unit_SetSTO(hnd, 0); // already set to 0
                           end;
                        end
                        else
                        // sto=0
                        begin
                           if (statetag and gc_statetag_move_walk<>0) then
                           begin
                              var stpx : Float = GetGameObjectStateTargetPositionXByHandle(hnd);
                              var stpz : Float = GetGameObjectStateTargetPositionZByHandle(hnd);
                              if (VectorDistance(stpx, 0, stpz, GetGameObjectPositionXByHandle(hnd), 0, GetGameObjectPositionZByHandle(hnd))>gc_objectEpsilonDist*4) then
                              begin
                                 var dirx : Float = stpx-GetGameObjectPositionXByHandle(hnd);
                                 var diry : Float;
                                 var dirz : Float = stpz-GetGameObjectPositionZByHandle(hnd);
                                 VectorNormalize(dirx, diry, dirz);
                                 var movemode : Integer;
                                 if (statetag and gc_statetag_weapon_none<>0) then
                                 movemode := gc_obj_order_move_mode_default
                                 else
                                 movemode := gc_obj_order_move_mode_attack;
                                 _unit_OrderMove(hnd, stpx, stpz, dirx, dirz, movemode, True);
                              end;
                           end;
                        end;
                     end;
                  end
               end;
            end;
         end;
      end;
   end;
   for pl:=0 to gc_MaxPlayerCount-1 do
   begin
      var plHnd : Integer = GetPlayerHandleByIndex(pl);
      for i:=GetPlayerGameObjectsCountByHandle(plHnd)-1 downto 0 do
      begin
         var hnd : Integer = GetGameObjectHandleByIndex(i, plHnd);
         var pobj : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_obj);
         if (pobj<>nil) then
         begin
            //var statetag : Integer = GetGameObjectStatesTagByHandle(hnd);
            var pinside : Pointer = _misc_GetObjectArgData(hnd, gc_argunit_inside);
            if (pinside<>nil) and (TIntegerList(pinside).GetCount>0) then
            begin
               var uid : Integer = FindUniqIdByGameObject(hnd);
               for j:=TIntegerList(pinside).GetCount-1 downto 0 do
               begin
                  var hndins : Integer = TIntegerList(pinside).Get(j);
                  if (not _misc_IsValidHandle(hndins)) then
                  begin
                     Log('RepairSession : invalid unit in inside list : unit removed from list');
                     TIntegerList(pinside).Delete(j);
                  end
                  else
                  begin
                     var pobjins : Pointer = _unit_GetTObj(hndins);
                     if (pobjins<>nil) then
                     begin
                        TObj(pobjins).insideofuid := uid;
                        //SetGameObjectSTOHandleByHandle(hndins, hnd);
                        _unit_SetSTO(hndins, hnd);
                     end;
                  end;
               end;
            end;
         end;
      end;
   end;

   //if (bMisc) and (bServer) then
   //_player_FixPlayersCounters; // includes _player_FixPlayerLists;

   TimeLog('RepairSession finish');
end;
