hedgewars/uAI.pas
branchhedgeroid
changeset 15515 7030706266df
parent 15447 6031c0cfec89
equal deleted inserted replaced
7861:bc7b6aa5d67a 15515:7030706266df
     1 (*
     1 (*
     2  * Hedgewars, a free turn based strategy game
     2  * Hedgewars, a free turn based strategy game
     3  * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
     3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
     4  *
     4  *
     5  * This program is free software; you can redistribute it and/or modify
     5  * This program is free software; you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation; version 2 of the License
     7  * the Free Software Foundation; version 2 of the License
     8  *
     8  *
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    12  * GNU General Public License for more details.
    13  *
    13  *
    14  * You should have received a copy of the GNU General Public License
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program; if not, write to the Free Software
    15  * along with this program; if not, write to the Free Software
    16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
    16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    17  *)
    17  *)
    18 
    18 
    19 {$INCLUDE "options.inc"}
    19 {$INCLUDE "options.inc"}
    20 
    20 
    21 unit uAI;
    21 unit uAI;
    28 procedure ProcessBot;
    28 procedure ProcessBot;
    29 procedure FreeActionsList;
    29 procedure FreeActionsList;
    30 
    30 
    31 implementation
    31 implementation
    32 uses uConsts, SDLh, uAIMisc, uAIAmmoTests, uAIActions,
    32 uses uConsts, SDLh, uAIMisc, uAIAmmoTests, uAIActions,
    33     uAmmos, SysUtils{$IFNDEF USE_SDLTHREADS} {$IFDEF UNIX}, cthreads{$ENDIF} {$ENDIF}, uTypes,
    33     uAmmos, uTypes,
    34     uVariables, uCommands, uUtils, uDebug, uAILandMarks;
    34     uVariables, uCommands, uUtils, uDebug, uAILandMarks,
       
    35     uGearsUtils;
    35 
    36 
    36 var BestActions: TActions;
    37 var BestActions: TActions;
    37     CanUseAmmo: array [TAmmoType] of boolean;
    38     CanUseAmmo: array [TAmmoType] of boolean;
    38     StopThinking: boolean;
    39     StopThinking: boolean;
    39 {$IFDEF USE_SDLTHREADS} 
       
    40     ThinkThread: PSDL_Thread = nil;
       
    41 {$ELSE}
       
    42     ThinkThread: TThreadID;
       
    43 {$ENDIF}
       
    44     hasThread: LongInt;
       
    45     StartTicks: Longword;
    40     StartTicks: Longword;
       
    41     ThreadSem: PSDL_Sem;
    46 
    42 
    47 procedure FreeActionsList;
    43 procedure FreeActionsList;
    48 begin
    44 begin
    49     AddFileLog('FreeActionsList called');
    45     AddFileLog('FreeActionsList called');
    50     if hasThread <> 0 then
    46 
    51     begin
    47     StopThinking:= true;
    52         AddFileLog('Waiting AI thread to finish');
    48     SDL_SemWait(ThreadSem);
    53         StopThinking:= true;
    49     SDL_SemPost(ThreadSem);
    54         repeat
    50 
    55             SDL_Delay(10)
    51     if CurrentHedgehog <> nil then
    56         until hasThread = 0
    52         with CurrentHedgehog^ do
    57     end;
    53             if Gear <> nil then
    58 
    54                 if BotLevel <> 0 then
    59     with CurrentHedgehog^ do
    55                     StopMessages(Gear^.Message);
    60         if Gear <> nil then
       
    61             if BotLevel <> 0 then
       
    62                 StopMessages(Gear^.Message);
       
    63 
    56 
    64     BestActions.Count:= 0;
    57     BestActions.Count:= 0;
    65     BestActions.Pos:= 0
    58     BestActions.Pos:= 0
    66 end;
    59 end;
    67 
    60 
    68 
    61 
    69 
       
    70 const cBranchStackSize = 12;
    62 const cBranchStackSize = 12;
    71 type TStackEntry = record
    63 type TStackEntry = record
    72                    WastedTicks: Longword;
       
    73                    MadeActions: TActions;
    64                    MadeActions: TActions;
    74                    Hedgehog: TGear;
    65                    Hedgehog: TGear;
    75                    end;
    66                    end;
    76 
    67 
    77 var Stack: record
    68 var Stack: record
    78            Count: Longword;
    69            Count: Longword;
    79            States: array[0..Pred(cBranchStackSize)] of TStackEntry;
    70            States: array[0..Pred(cBranchStackSize)] of TStackEntry;
    80            end;
    71            end;
    81 
    72 
    82 function Push(Ticks: Longword; const Actions: TActions; const Me: TGear; Dir: integer): boolean;
    73 function Push(const Actions: TActions; const Me: TGear; Dir: integer): boolean;
    83 var bRes: boolean;
    74 var bRes: boolean;
    84 begin
    75 begin
    85     bRes:= (Stack.Count < cBranchStackSize) and (Actions.Count < MAXACTIONS - 5);
    76     bRes:= (Stack.Count < cBranchStackSize) and (Actions.Count < MAXACTIONS - 5);
    86     if bRes then
    77     if bRes then
    87         with Stack.States[Stack.Count] do
    78         with Stack.States[Stack.Count] do
    88             begin
    79             begin
    89             WastedTicks:= Ticks;
       
    90             MadeActions:= Actions;
    80             MadeActions:= Actions;
    91             Hedgehog:= Me;
    81             Hedgehog:= Me;
    92             Hedgehog.Message:= Dir;
    82             Hedgehog.Message:= Dir;
    93             inc(Stack.Count)
    83             inc(Stack.Count)
    94             end;
    84             end;
    95     Push:= bRes
    85     Push:= bRes
    96 end;
    86 end;
    97 
    87 
    98 procedure Pop(var Ticks: Longword; var Actions: TActions; var Me: TGear);
    88 procedure Pop(var Actions: TActions; var Me: TGear);
    99 begin
    89 begin
   100     dec(Stack.Count);
    90     dec(Stack.Count);
   101     with Stack.States[Stack.Count] do
    91     with Stack.States[Stack.Count] do
   102         begin
    92         begin
   103         Ticks:= WastedTicks;
       
   104         Actions:= MadeActions;
    93         Actions:= MadeActions;
   105         Me:= Hedgehog
    94         Me:= Hedgehog
   106         end
    95         end
   107 end;
    96 end;
   108 
    97 
   109 
    98 
   110 
    99 
   111 procedure TestAmmos(var Actions: TActions; Me: PGear; rareChecks: boolean);
   100 procedure TestAmmos(var Actions: TActions; Me: PGear; rareChecks: boolean);
   112 var BotLevel: Byte;
   101 var BotLevel: Byte;
   113     ap: TAttackParams;
   102     ap: TAttackParams;
   114     Score, i, dAngle: LongInt;
   103     Score, i, t, n, dAngle: LongInt;
   115     a, aa: TAmmoType;
   104     a, aa: TAmmoType;
       
   105     useThisActions: boolean;
   116 begin
   106 begin
   117 BotLevel:= Me^.Hedgehog^.BotLevel;
   107 BotLevel:= Me^.Hedgehog^.BotLevel;
   118 windSpeed:= hwFloat2Float(cWindSpeed);
   108 windSpeed:= hwFloat2Float(cWindSpeed);
       
   109 useThisActions:= false;
       
   110 Me^.AIHints:= Me^.AIHints and (not aihAmmosChanged);
   119 
   111 
   120 for i:= 0 to Pred(Targets.Count) do
   112 for i:= 0 to Pred(Targets.Count) do
   121     if (Targets.ar[i].Score >= 0) and (not StopThinking) then
   113     if (Targets.ar[i].Score >= 0) and (not StopThinking) then
   122         begin
   114         begin
   123         with Me^.Hedgehog^ do
   115         with Me^.Hedgehog^ do
   124             a:= CurAmmoType;
   116             a:= CurAmmoType;
   125         aa:= a;
   117         aa:= a;
   126 {$IFDEF USE_SDLTHREADS}
   118         SDL_delay(0); // hint to let the context switch run
   127         SDL_delay(0);    //ThreadSwitch was only a hint
       
   128 {$ELSE}
       
   129         ThreadSwitch();
       
   130 {$ENDIF}       
       
   131         repeat
   119         repeat
   132         if (CanUseAmmo[a]) 
   120         if (CanUseAmmo[a])
   133             and ((not rareChecks) or ((AmmoTests[a].flags and amtest_Rare) = 0)) 
   121             and ((not rareChecks) or ((AmmoTests[a].flags and amtest_Rare) = 0))
   134             and ((i = 0) or ((AmmoTests[a].flags and amtest_NoTarget) = 0)) 
   122             and ((i = 0) or ((AmmoTests[a].flags and amtest_NoTarget) = 0))
   135             then
   123             then
   136             begin
   124             begin
   137 {$HINTS OFF}
   125 {$HINTS OFF}
   138             Score:= AmmoTests[a].proc(Me, Targets.ar[i].Point, BotLevel, ap);
   126             Score:= AmmoTests[a].proc(Me, Targets.ar[i], BotLevel, ap);
   139 {$HINTS ON}
   127 {$HINTS ON}
   140             if Actions.Score + Score > BestActions.Score then
   128             if (Score > BadTurn) and (Actions.Score + Score > BestActions.Score) then
   141                 if (BestActions.Score < 0) or (Actions.Score + Score > BestActions.Score + Byte(BotLevel) * 2048) then
   129                 if (BestActions.Score < 0) or (Actions.Score + Score > BestActions.Score + Byte(BotLevel - 1) * 2048) then
   142                     begin
   130                     begin
   143                     BestActions:= Actions;
   131                     if useThisActions then
   144                     inc(BestActions.Score, Score);
   132                         begin
   145                     BestActions.isWalkingToABetterPlace:= false;
   133                         BestActions.Count:= Actions.Count
       
   134                         end
       
   135                     else
       
   136                         begin
       
   137                         BestActions:= Actions;
       
   138                         BestActions.isWalkingToABetterPlace:= false;
       
   139                         useThisActions:= true
       
   140                         end;
       
   141 
       
   142                     BestActions.Score:= Actions.Score + Score;
       
   143 
       
   144                     // if not between shots, activate invulnerability/vampirism if available
       
   145                     if CurrentHedgehog^.MultiShootAttacks = 0 then
       
   146                         begin
       
   147                         if (HHHasAmmo(Me^.Hedgehog^, amInvulnerable) > 0) and (Me^.Hedgehog^.Effects[heInvulnerable] = 0) then
       
   148                             begin
       
   149                             AddAction(BestActions, aia_Weapon, Longword(amInvulnerable), 80, 0, 0);
       
   150                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
       
   151                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
       
   152                             end;
       
   153 
       
   154                         if (HHHasAmmo(Me^.Hedgehog^, amExtraDamage) > 0) and (cDamageModifier <> _1_5) then
       
   155                             begin
       
   156                             AddAction(BestActions, aia_Weapon, Longword(amExtraDamage), 80, 0, 0);
       
   157                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
       
   158                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
       
   159                             end;
       
   160                         if (HHHasAmmo(Me^.Hedgehog^, amVampiric) > 0) and (not cVampiric) then
       
   161                             begin
       
   162                             AddAction(BestActions, aia_Weapon, Longword(amVampiric), 80, 0, 0);
       
   163                             AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
       
   164                             AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
       
   165                             end;
       
   166                         end;
   146 
   167 
   147                     AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0);
   168                     AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0);
       
   169 
       
   170                     if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
       
   171                         begin
       
   172                         AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY)
       
   173                         end;
   148 
   174 
   149                     if (ap.Angle > 0) then
   175                     if (ap.Angle > 0) then
   150                         AddAction(BestActions, aia_LookRight, 0, 200, 0, 0)
   176                         AddAction(BestActions, aia_LookRight, 0, 200, 0, 0)
   151                     else if (ap.Angle < 0) then
   177                     else if (ap.Angle < 0) then
   152                         AddAction(BestActions, aia_LookLeft, 0, 200, 0, 0);
   178                         AddAction(BestActions, aia_LookLeft, 0, 200, 0, 0);
   153                     
   179 
   154                     if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then
   180                     if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then
   155                         AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0);
   181                         AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0);
   156                         
   182 
   157                     if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then
   183                     if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then
   158                         begin
   184                         begin
   159                         dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle);
   185                         dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle);
   160                         if dAngle > 0 then
   186                         if dAngle > 0 then
   161                             begin
   187                             begin
   166                             begin
   192                             begin
   167                             AddAction(BestActions, aia_Down, aim_push, 300 + random(250), 0, 0);
   193                             AddAction(BestActions, aia_Down, aim_push, 300 + random(250), 0, 0);
   168                             AddAction(BestActions, aia_Down, aim_release, -dAngle, 0, 0)
   194                             AddAction(BestActions, aia_Down, aim_release, -dAngle, 0, 0)
   169                             end
   195                             end
   170                         end;
   196                         end;
   171                         
   197 
   172                     if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
       
   173                         begin
       
   174                         AddAction(BestActions, aia_Put, 0, 1, ap.AttackPutX, ap.AttackPutY)
       
   175                         end;
       
   176                         
       
   177                     if (Ammoz[a].Ammo.Propz and ammoprop_OscAim) <> 0 then
   198                     if (Ammoz[a].Ammo.Propz and ammoprop_OscAim) <> 0 then
   178                         begin
   199                         begin
   179                         AddAction(BestActions, aia_attack, aim_push, 350 + random(200), 0, 0);
   200                         AddAction(BestActions, aia_attack, aim_push, 350 + random(200), 0, 0);
   180                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
   201                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
   181                          
   202 
   182                         if abs(ap.Angle) > 32 then
   203                         if abs(ap.Angle) > 32 then
   183                            begin
   204                            begin
   184                            AddAction(BestActions, aia_Down, aim_push, 100 + random(150), 0, 0);
   205                            AddAction(BestActions, aia_Down, aim_push, 100 + random(150), 0, 0);
   185                            AddAction(BestActions, aia_Down, aim_release, 32, 0, 0);
   206                            AddAction(BestActions, aia_Down, aim_release, 32, 0, 0);
   186                            end;
   207                            end;
   187                         
   208 
   188                         AddAction(BestActions, aia_waitAngle, ap.Angle, 250, 0, 0);
   209                         AddAction(BestActions, aia_waitAngle, ap.Angle, 250, 0, 0);
   189                         AddAction(BestActions, aia_attack, aim_push, 1, 0, 0);
   210                         AddAction(BestActions, aia_attack, aim_push, 1, 0, 0);
   190                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
   211                         AddAction(BestActions, aia_attack, aim_release, 1, 0, 0);
   191                         end else
   212                         end else
   192                         if (Ammoz[a].Ammo.Propz and ammoprop_AttackingPut) = 0 then
   213                         if (Ammoz[a].Ammo.Propz and ammoprop_AttackingPut) = 0 then
   193                             begin
   214                             begin
       
   215                             if (AmmoTests[a].flags and amtest_MultipleAttacks) = 0 then
       
   216                                 n:= 1 else n:= ap.AttacksNum;
       
   217 
   194                             AddAction(BestActions, aia_attack, aim_push, 650 + random(300), 0, 0);
   218                             AddAction(BestActions, aia_attack, aim_push, 650 + random(300), 0, 0);
       
   219                             for t:= 2 to n do
       
   220                                 begin
       
   221                                 AddAction(BestActions, aia_attack, aim_push, 150, 0, 0);
       
   222                                 AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0);
       
   223                                 end;
   195                             AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0);
   224                             AddAction(BestActions, aia_attack, aim_release, ap.Power, 0, 0);
   196                             end;
   225                             end;
   197 
   226 
   198                     if (Ammoz[a].Ammo.Propz and ammoprop_Track) <> 0 then
   227                     if (Ammoz[a].Ammo.Propz and ammoprop_Track) <> 0 then
   199                         begin
   228                         begin
   215 end;
   244 end;
   216 
   245 
   217 procedure Walk(Me: PGear; var Actions: TActions);
   246 procedure Walk(Me: PGear; var Actions: TActions);
   218 const FallPixForBranching = cHHRadius;
   247 const FallPixForBranching = cHHRadius;
   219 var
   248 var
   220     ticks, maxticks, oldticks, steps, tmp: Longword;
   249     maxticks, oldticks, steps, tmp: Longword;
   221     BaseRate, BestRate, Rate: integer;
   250     BaseRate, BestRate, Rate: LongInt;
   222     GoInfo: TGoInfo;
   251     GoInfo: TGoInfo;
   223     CanGo: boolean;
   252     CanGo: boolean;
   224     AltMe: TGear;
   253     AltMe: TGear;
   225     BotLevel: Byte;
   254     BotLevel: Byte;
   226     a: TAmmoType;
   255     a: TAmmoType;
   227 begin
   256     isAfterAttack: boolean;
   228 ticks:= 0;
   257 begin
       
   258 Actions.ticks:= 0;
   229 oldticks:= 0; // avoid compiler hint
   259 oldticks:= 0; // avoid compiler hint
   230 Stack.Count:= 0;
   260 Stack.Count:= 0;
   231 
   261 
   232 clearAllMarks;
   262 clearAllMarks;
   233 
   263 
   234 for a:= Low(TAmmoType) to High(TAmmoType) do
   264 for a:= Low(TAmmoType) to High(TAmmoType) do
   235     CanUseAmmo[a]:= Assigned(AmmoTests[a].proc) and (HHHasAmmo(Me^.Hedgehog^, a) > 0);
   265     CanUseAmmo[a]:= Assigned(AmmoTests[a].proc) and (HHHasAmmo(Me^.Hedgehog^, a) > 0);
   236 
   266 
   237 BotLevel:= Me^.Hedgehog^.BotLevel;
   267 BotLevel:= Me^.Hedgehog^.BotLevel;
   238 
   268 
   239 if (Me^.State and gstAttacked) = 0 then
   269 isAfterAttack:= ((Me^.State and gstAttacked) <> 0) and ((GameFlags and gfInfAttack) = 0);
   240     maxticks:= Max(0, TurnTimeLeft - 5000 - LongWord(4000 * BotLevel))
   270 if isAfterAttack then
       
   271     maxticks:= Max(0, TurnTimeLeft - 500)
   241 else
   272 else
   242     maxticks:= TurnTimeLeft;
   273     maxticks:= Max(0, TurnTimeLeft - 5000 - LongWord(4000 * BotLevel));
   243 
   274 
   244 if (Me^.State and gstAttacked) = 0 then
   275 if not isAfterAttack then
   245     TestAmmos(Actions, Me, false);
   276     TestAmmos(Actions, Me, false);
   246     
   277 
   247 BestRate:= RatePlace(Me);
   278 BestRate:= RatePlace(Me);
   248 BaseRate:= Max(BestRate, 0);
   279 BaseRate:= Max(BestRate, 0);
   249 
   280 
   250 // switch to 'skip' if we can't move because of mouse cursor being shown
   281 // switch to 'skip' if we cannot move because of mouse cursor being shown
   251 if (Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
   282 if (Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
   252     AddAction(Actions, aia_Weapon, Longword(amSkip), 100 + random(200), 0, 0);
   283     AddAction(Actions, aia_Weapon, Longword(amSkip), 100 + random(200), 0, 0);
   253     
   284 
   254 if ((CurrentHedgehog^.MultiShootAttacks = 0) or ((Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NoMoveAfter) = 0)) 
   285 if ((CurrentHedgehog^.MultiShootAttacks = 0) or ((Ammoz[Me^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NoMoveAfter) = 0))
   255     and (GameFlags and gfArtillery = 0) then
   286     and (CurrentHedgehog^.Effects[heArtillery] = 0) and (cGravityf <> 0) then
   256     begin
   287     begin
   257     tmp:= random(2) + 1;
   288     tmp:= random(2) + 1;
   258     Push(0, Actions, Me^, tmp);
   289     Push(Actions, Me^, tmp);
   259     Push(0, Actions, Me^, tmp xor 3);
   290     Push(Actions, Me^, tmp xor 3);
   260     
   291 
   261     while (Stack.Count > 0) and (not StopThinking) do
   292     while (Stack.Count > 0) and (not StopThinking) do
   262         begin
   293         begin
   263         Pop(ticks, Actions, Me^);
   294         Pop(Actions, Me^);
   264 
   295 
   265         AddAction(Actions, Me^.Message, aim_push, 250, 0, 0);
   296         AddAction(Actions, Me^.Message, aim_push, 250, 0, 0);
   266         if (Me^.Message and gmLeft) <> 0 then
   297         if (Me^.Message and gmLeft) <> 0 then
   267             AddAction(Actions, aia_WaitXL, hwRound(Me^.X), 0, 0, 0)
   298             AddAction(Actions, aia_WaitXL, hwRound(Me^.X), 0, 0, 0)
   268         else
   299         else
   269             AddAction(Actions, aia_WaitXR, hwRound(Me^.X), 0, 0, 0);
   300             AddAction(Actions, aia_WaitXR, hwRound(Me^.X), 0, 0, 0);
   270         
   301 
   271         steps:= 0;
   302         steps:= 0;
   272 
   303 
   273         while (not StopThinking) do
   304         while (not StopThinking) do
   274             begin
   305             begin
   275     {$HINTS OFF}
   306     {$HINTS OFF}
   276             CanGo:= HHGo(Me, @AltMe, GoInfo);
   307             CanGo:= HHGo(Me, @AltMe, GoInfo);
   277     {$HINTS ON}
   308     {$HINTS ON}
   278             oldticks:= ticks;
   309             oldticks:= Actions.ticks;
   279             inc(ticks, GoInfo.Ticks);
   310             inc(Actions.ticks, GoInfo.Ticks);
   280             if ticks > maxticks then
   311             if (Actions.ticks > maxticks) or (TurnTimeLeft < BestActions.ticks + 5000) then
       
   312             begin
       
   313                 if (BotLevel < 5)
       
   314                         and (not isAfterAttack)
       
   315                         and (BestActions.Score > 0) // we have a good move
       
   316                         and (TurnTimeLeft < BestActions.ticks + 5000) // we won't have a lot of time after attack
       
   317                         and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) > 0) // but can use extra time
       
   318                 then
       
   319                 begin
       
   320                     BestActions.Count:= 0;
       
   321                     AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0);
       
   322                     AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
       
   323                     AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
       
   324                 end;
       
   325 
   281                 break;
   326                 break;
   282 
   327             end;
   283             if (BotLevel < 5) 
   328 
   284                 and (GoInfo.JumpType = jmpHJump) 
   329             if (BotLevel < 5)
       
   330                 and (GoInfo.JumpType = jmpHJump)
   285                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped))
   331                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped))
   286                 then // hjump support
   332                 then // hjump support
   287                 begin
   333                 begin
   288                 // check if we could go backwards and maybe ljump over a gap after this hjump
   334                 // check if we could go backwards and maybe ljump over a gap after this hjump
   289                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped);
   335                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markHJumped);
   290                 if Push(ticks, Actions, AltMe, Me^.Message xor 3) then
   336                 if Push(Actions, AltMe, Me^.Message xor 3) then
   291                     begin
   337                     begin
   292                     with Stack.States[Pred(Stack.Count)] do
   338                     with Stack.States[Pred(Stack.Count)] do
   293                         begin
   339                         begin
   294                         if Me^.dX.isNegative then
   340                         if (Me^.Message and gmLeft) <> 0 then
   295                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0)
   341                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0)
   296                         else
   342                         else
   297                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0);
   343                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0);
   298                             
   344 
   299                         AddAction(MadeActions, aia_HJump, 0, 305 + random(50), 0, 0);
   345                         AddAction(MadeActions, aia_HJump, 0, 305 + random(50), 0, 0);
   300                         AddAction(MadeActions, aia_HJump, 0, 350, 0, 0);
   346                         AddAction(MadeActions, aia_HJump, 0, 350, 0, 0);
   301                         
   347                         end;
   302                         if Me^.dX.isNegative then
   348                     // but first check walking forward
       
   349                     Push(Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message)
       
   350                     end;
       
   351                 end;
       
   352             if (BotLevel < 3)
       
   353                 and (GoInfo.JumpType = jmpLJump)
       
   354                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped))
       
   355                 then // ljump support
       
   356                 begin
       
   357                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped);
       
   358                 // at final check where we go after jump walking backward
       
   359                 if Push(Actions, AltMe, Me^.Message xor 3) then
       
   360                     with Stack.States[Pred(Stack.Count)] do
       
   361                         begin
       
   362                         if (Me^.Message and gmLeft) <> 0 then
   303                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0)
   363                             AddAction(MadeActions, aia_LookLeft, 0, 200, 0, 0)
   304                         else
   364                         else
   305                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0);
   365                             AddAction(MadeActions, aia_LookRight, 0, 200, 0, 0);
       
   366 
       
   367                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
   306                         end;
   368                         end;
   307                     // but first check walking forward
   369 
   308                     Push(ticks, Stack.States[Pred(Stack.Count)].MadeActions, AltMe, Me^.Message)
   370                 // push current position so we proceed from it after checking jump+forward walk opportunities
   309                     end;
   371                 if CanGo then Push(Actions, Me^, Me^.Message);
   310                 end;
   372 
   311             if (BotLevel < 3) 
   373                 // first check where we go after jump walking forward
   312                 and (GoInfo.JumpType = jmpLJump) 
   374                 if Push(Actions, AltMe, Me^.Message) then
   313                 and (not checkMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped))
       
   314                 then // ljump support
       
   315                 begin
       
   316                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markLJumped);
       
   317                 // at final check where we go after jump walking backward
       
   318                 if Push(ticks, Actions, AltMe, Me^.Message xor 3) then
       
   319                     with Stack.States[Pred(Stack.Count)] do
   375                     with Stack.States[Pred(Stack.Count)] do
   320                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
   376                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
   321 
   377 
   322                 // push current position so we proceed from it after checking jump+forward walk opportunities
       
   323                 if CanGo then Push(ticks, Actions, Me^, Me^.Message);
       
   324                 
       
   325                 // first check where we go after jump walking forward
       
   326                 if Push(ticks, Actions, AltMe, Me^.Message) then
       
   327                     with Stack.States[Pred(Stack.Count)] do
       
   328                         AddAction(MadeActions, aia_LJump, 0, 305 + random(50), 0, 0);
       
   329                 break
   378                 break
   330                 end;
   379                 end;
   331 
   380 
   332             // 'not CanGO' means we can't go straight, possible jumps are checked above
   381             // 'not CanGO' means we cannot go straight, possible jumps are checked above
   333             if not CanGo then
   382             if not CanGo then
   334                 break;
   383                 break;
   335             
   384 
   336              inc(steps);
   385              inc(steps);
   337              Actions.actions[Pred(Actions.Count)].Param:= hwRound(Me^.X);
   386              Actions.actions[Pred(Actions.Count)].Param:= hwRound(Me^.X);
   338              Rate:= RatePlace(Me);
   387              Rate:= RatePlace(Me);
   339              if Rate > BestRate then
   388              if Rate > BestRate then
   340                 begin
   389                 begin
   341                 BestActions:= Actions;
   390                 BestActions:= Actions;
   342                 BestActions.isWalkingToABetterPlace:= true;
   391                 BestActions.isWalkingToABetterPlace:= true;
   343                 BestRate:= Rate;
   392                 BestRate:= Rate;
   344                 Me^.State:= Me^.State or gstAttacked // we have better place, go there and do not use ammo
   393                 isAfterAttack:= true // we have better place, go there and do not use ammo
   345                 end
   394                 end
   346             else if Rate < BestRate then
   395             else if Rate < BestRate then
   347                 break;
   396                 break;
   348                 
   397 
   349             if ((Me^.State and gstAttacked) = 0) and ((steps mod 4) = 0) then
   398             if (not isAfterAttack) and ((steps mod 4) = 0) then
   350                 begin
   399                 begin
   351                 if (steps > 4) and checkMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere) then
   400                 if (steps > 4) and checkMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere) then
   352                     break;                    
   401                     break;
   353                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere);
   402                 addMark(hwRound(Me^.X), hwRound(Me^.Y), markWalkedHere);
   354 
   403 
   355                 TestAmmos(Actions, Me, ticks shr 12 = oldticks shr 12);
   404                 TestAmmos(Actions, Me, Actions.ticks shr 12 = oldticks shr 12);
   356                 end;
   405                 end;
   357                 
   406 
   358             if GoInfo.FallPix >= FallPixForBranching then
   407             if GoInfo.FallPix >= FallPixForBranching then
   359                 Push(ticks, Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right
   408                 Push(Actions, Me^, Me^.Message xor 3); // aia_Left xor 3 = aia_Right
   360             end {while};
   409             end {while};
   361 
   410 
   362         if BestRate > BaseRate then
   411         if BestRate > BaseRate then
   363             exit
   412             exit
   364         end {while}
   413         end {while}
   365     end {if}
   414     end {if}
   366 end;
   415 end;
   367 
   416 
   368 function Think(Me: Pointer): ptrint;
   417 function Think(Me: PGear): LongInt; cdecl; export;
   369 var BackMe, WalkMe: TGear;
   418 var BackMe, WalkMe: TGear;
   370     switchCount: LongInt;
   419     switchCount: LongInt;
   371     StartTicks, currHedgehogIndex, itHedgehog, switchesNum, i: Longword;
   420     currHedgehogIndex, itHedgehog, switchesNum, i: Longword;
   372     switchImmediatelyAvailable: boolean;
   421     switchImmediatelyAvailable: boolean;
   373     Actions: TActions;
   422     Actions: TActions;
   374 begin
   423 begin
   375 InterlockedIncrement(hasThread);
   424 dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
   376 StartTicks:= GameTicks;
   425 StartTicks:= GameTicks;
       
   426 
   377 currHedgehogIndex:= CurrentTeam^.CurrHedgehog;
   427 currHedgehogIndex:= CurrentTeam^.CurrHedgehog;
   378 itHedgehog:= currHedgehogIndex;
   428 itHedgehog:= currHedgehogIndex;
   379 switchesNum:= 0;
   429 switchesNum:= 0;
   380 
   430 
   381 switchImmediatelyAvailable:= (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtSwitcher);
   431 switchImmediatelyAvailable:= (CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtSwitcher);
   382 if PGear(Me)^.Hedgehog^.BotLevel <> 5 then
   432 if Me^.Hedgehog^.BotLevel <> 5 then
   383     switchCount:= HHHasAmmo(PGear(Me)^.Hedgehog^, amSwitch)
   433     switchCount:= HHHasAmmo(PGear(Me)^.Hedgehog^, amSwitch)
   384 else switchCount:= 0;
   434 else switchCount:= 0;
   385 
   435 
   386 if (PGear(Me)^.State and gstAttacked) = 0 then
   436 if ((Me^.State and gstAttacked) = 0) or isInMultiShoot or bonuses.activity or ((Me^.AIHints and aihAmmosChanged) <> 0) then
   387     if Targets.Count > 0 then
   437     if Targets.Count > 0 then
   388         begin
   438         begin
   389         // iterate over current team hedgehogs
   439         // iterate over current team hedgehogs
   390         repeat
   440         repeat
   391             WalkMe:= CurrentTeam^.Hedgehogs[itHedgehog].Gear^;
   441             WalkMe:= CurrentTeam^.Hedgehogs[itHedgehog].Gear^;
   393             Actions.Count:= 0;
   443             Actions.Count:= 0;
   394             Actions.Pos:= 0;
   444             Actions.Pos:= 0;
   395             Actions.Score:= 0;
   445             Actions.Score:= 0;
   396             if switchesNum > 0 then
   446             if switchesNum > 0 then
   397                 begin
   447                 begin
   398                 if not switchImmediatelyAvailable  then
   448                 if (not switchImmediatelyAvailable)  then
   399                     begin
   449                     begin
   400                     // when AI has to use switcher, make it cost smth unless they have a lot of switches
   450                     // when AI has to use switcher, make it cost smth unless they have a lot of switches
   401                     if (switchCount < 10) then Actions.Score:= (-27+switchCount*3)*4000;
   451                     if (switchCount < 10) then Actions.Score:= (-27+switchCount*3)*4000;
   402                     AddAction(Actions, aia_Weapon, Longword(amSwitch), 300 + random(200), 0, 0);                    
   452                     AddAction(Actions, aia_Weapon, Longword(amSwitch), 300 + random(200), 0, 0);
   403                     AddAction(Actions, aia_attack, aim_push, 300 + random(300), 0, 0);
   453                     AddAction(Actions, aia_attack, aim_push, 300 + random(300), 0, 0);
   404                     AddAction(Actions, aia_attack, aim_release, 1, 0, 0);
   454                     AddAction(Actions, aia_attack, aim_release, 1, 0, 0);
   405                     end;
   455                     end;
   406                 for i:= 1 to switchesNum do
   456                 for i:= 1 to switchesNum do
   407                     AddAction(Actions, aia_Switch, 0, 300 + random(200), 0, 0);
   457                     AddAction(Actions, aia_Switch, 0, 300 + random(200), 0, 0);
   409             Walk(@WalkMe, Actions);
   459             Walk(@WalkMe, Actions);
   410 
   460 
   411             // find another hog in team
   461             // find another hog in team
   412             repeat
   462             repeat
   413                 itHedgehog:= Succ(itHedgehog) mod CurrentTeam^.HedgehogsNumber;
   463                 itHedgehog:= Succ(itHedgehog) mod CurrentTeam^.HedgehogsNumber;
   414             until (itHedgehog = currHedgehogIndex) or (CurrentTeam^.Hedgehogs[itHedgehog].Gear <> nil);
   464             until (itHedgehog = currHedgehogIndex) or ((CurrentTeam^.Hedgehogs[itHedgehog].Gear <> nil) and (CurrentTeam^.Hedgehogs[itHedgehog].Effects[heFrozen]=0));
   415 
       
   416 
   465 
   417             inc(switchesNum);
   466             inc(switchesNum);
   418         until (not (switchImmediatelyAvailable or (switchCount > 0)))
   467         until (not (switchImmediatelyAvailable or (switchCount > 0)))
   419             or StopThinking 
   468             or StopThinking
   420             or (itHedgehog = currHedgehogIndex)
   469             or (itHedgehog = currHedgehogIndex)
   421             or BestActions.isWalkingToABetterPlace;
   470             or BestActions.isWalkingToABetterPlace;
   422 
   471 
   423         if (StartTicks > GameTicks - 1500) and (not StopThinking) then
   472         if (StartTicks > GameTicks - 1500) and (not StopThinking) then
   424             SDL_Delay(1000);
   473             SDL_Delay(700);
   425 
   474 
   426         if (BestActions.Score < -1023) and (not BestActions.isWalkingToABetterPlace) then
   475         if (BestActions.Score < -1023) and (not BestActions.isWalkingToABetterPlace) then
   427             begin
   476             begin
   428             BestActions.Count:= 0;
   477             BestActions.Count:= 0;
   429             AddAction(BestActions, aia_Skip, 0, 250, 0, 0);
   478 
       
   479             FillBonuses(false);
       
   480 
       
   481             // Hog has no idea what to do. Use tardis or skip
       
   482             if (not bonuses.activity) and ((Me^.AIHints and aihAmmosChanged) = 0) then
       
   483                 if (((GameFlags and gfInfAttack) <> 0) or (CurrentHedgehog^.MultiShootAttacks = 0)) and (HHHasAmmo(Me^.Hedgehog^, amTardis) > 0) and (CanUseTardis(Me^.Hedgehog^.Gear)) and (random(4) < 3) then
       
   484                     // Tardis brings hog to a random place. Perfect for clueless AI
       
   485                     begin
       
   486                     AddAction(BestActions, aia_Weapon, Longword(amTardis), 80, 0, 0);
       
   487                     AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
       
   488                     AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
       
   489                     end
       
   490                 else
       
   491                     AddAction(BestActions, aia_Skip, 0, 250, 0, 0);
       
   492             Me^.AIHints := ME^.AIHints and (not aihAmmosChanged);
   430             end;
   493             end;
   431 
   494 
   432         end else
   495         end else SDL_Delay(100)
   433 else
   496 else
   434     begin
   497     begin
   435     BackMe:= PGear(Me)^;
   498     BackMe:= Me^;
   436     while (not StopThinking) and (BestActions.Count = 0) do
   499     i:= 4;
       
   500     while (not StopThinking) and (BestActions.Count = 0) and (i > 0) do
   437         begin
   501         begin
       
   502 
   438 (*
   503 (*
   439         // Maybe this would get a bit of movement out of them? Hopefully not *toward* water. Need to check how often he'd choose that strategy
   504         // Maybe this would get a bit of movement out of them? Hopefully not *toward* water. Need to check how often he'd choose that strategy
   440         if SuddenDeathDmg and ((hwRound(BackMe.Y)+cWaterRise*2) > cWaterLine) then
   505         if SuddenDeathDmg and ((hwRound(BackMe.Y)+cWaterRise*2) > cWaterLine) then
   441             AddBonus(hwRound(BackMe.X), hwRound(BackMe.Y), 250, -40);
   506             AddBonus(hwRound(BackMe.X), hwRound(BackMe.Y), 250, -40);
   442 *)
   507 *)
       
   508 
   443         FillBonuses(true);
   509         FillBonuses(true);
   444         WalkMe:= BackMe;
   510         WalkMe:= BackMe;
   445         Actions.Count:= 0;
   511         Actions.Count:= 0;
   446         Actions.Pos:= 0;
   512         Actions.Pos:= 0;
   447         Actions.Score:= 0;
   513         Actions.Score:= 0;
   448         Walk(@WalkMe, Actions);
   514         Walk(@WalkMe, Actions);
       
   515         if not bonuses.activity then dec(i);
   449         if not StopThinking then
   516         if not StopThinking then
   450             SDL_Delay(100)
   517             SDL_Delay(100)
   451         end
   518         end
   452     end;
   519     end;
   453 
   520 
   454 PGear(Me)^.State:= PGear(Me)^.State and (not gstHHThinking);
   521 Me^.State:= Me^.State and (not gstHHThinking);
   455 Think:= 0;
   522 Think:= 0;
   456 InterlockedDecrement(hasThread)
   523 SDL_SemPost(ThreadSem);
   457 end;
   524 end;
   458 
   525 
   459 procedure StartThink(Me: PGear);
   526 procedure StartThink(Me: PGear);
       
   527 var ThinkThread: PSDL_Thread;
   460 begin
   528 begin
   461 if ((Me^.State and (gstAttacking or gstHHJumping or gstMoving)) <> 0)
   529 if ((Me^.State and (gstAttacking or gstHHJumping or gstMoving)) <> 0)
   462 or isInMultiShoot then
   530 or isInMultiShoot then
   463     exit;
   531     exit;
   464 
   532 
       
   533 SDL_SemWait(ThreadSem);
   465 //DeleteCI(Me); // this will break demo/netplay
   534 //DeleteCI(Me); // this will break demo/netplay
   466 
   535 
   467 Me^.State:= Me^.State or gstHHThinking;
   536 Me^.State:= Me^.State or gstHHThinking;
   468 Me^.Message:= 0;
   537 Me^.Message:= 0;
   469 
   538 
   480     begin
   549     begin
   481     OutError('AI: no targets!?', false);
   550     OutError('AI: no targets!?', false);
   482     exit
   551     exit
   483     end;
   552     end;
   484 
   553 
   485 FillBonuses((Me^.State and gstAttacked) <> 0);
   554 FillBonuses(((Me^.State and gstAttacked) <> 0) and (not isInMultiShoot) and ((GameFlags and gfInfAttack) = 0));
   486 AddFileLog('Enter Think Thread');
   555 
   487 {$IFDEF USE_SDLTHREADS}
   556 ThinkThread:= SDL_CreateThread(@Think, PChar('think'), Me);
   488 ThinkThread := SDL_CreateThread(@Think{$IFDEF SDL13}, nil{$ENDIF}, Me);
   557 SDL_DetachThread(ThinkThread);
   489 {$ELSE}
   558 end;
   490 BeginThread(@Think, Me, ThinkThread);
   559 
       
   560 {$IFDEF DEBUGAI}
       
   561 var scoreShown: boolean = false;
   491 {$ENDIF}
   562 {$ENDIF}
   492 AddFileLog('Thread started');
       
   493 end;
       
   494 
       
   495 //var scoreShown: boolean = false;
       
   496 
   563 
   497 procedure ProcessBot;
   564 procedure ProcessBot;
   498 const cStopThinkTime = 40;
   565 const cStopThinkTime = 40;
   499 begin
   566 begin
   500 with CurrentHedgehog^ do
   567 with CurrentHedgehog^ do
   501     if (Gear <> nil)
   568     if (Gear <> nil)
   502     and ((Gear^.State and gstHHDriven) <> 0)
   569     and ((Gear^.State and gstHHDriven) <> 0)
   503     and (TurnTimeLeft < cHedgehogTurnTime - 50) then
   570     and ((TurnTimeLeft < cHedgehogTurnTime - 50) or (TurnTimeLeft > cHedgehogTurnTime)) then
   504         if ((Gear^.State and gstHHThinking) = 0) then
   571         if ((Gear^.State and gstHHThinking) = 0) then
   505             if (BestActions.Pos >= BestActions.Count)
   572             if (BestActions.Pos >= BestActions.Count)
   506             and (TurnTimeLeft > cStopThinkTime) then
   573             and (TurnTimeLeft > cStopThinkTime) then
   507                 begin
   574                 begin
   508                 if Gear^.Message <> 0 then
   575                 if Gear^.Message <> 0 then
   509                     begin
   576                     begin
   510                     StopMessages(Gear^.Message);
   577                     StopMessages(Gear^.Message);
   511                     TryDo((Gear^.Message and gmAllStoppable) = 0, 'Engine bug: AI may break demos playing', true);
   578                     if checkFails((Gear^.Message and gmAllStoppable) = 0, 'Engine bug: AI may break demos playing', true) then exit;
   512                     end;
   579                     end;
   513                     
   580 
   514                 if Gear^.Message <> 0 then
   581                 if Gear^.Message <> 0 then
   515                     exit;
   582                     exit;
   516                     
   583 
   517                 //scoreShown:= false;   
   584 {$IFDEF DEBUGAI}
       
   585                 scoreShown:= false;
       
   586 {$ENDIF}
   518                 StartThink(Gear);
   587                 StartThink(Gear);
   519                 StartTicks:= GameTicks
   588                 StartTicks:= GameTicks
   520                 
   589 
   521             end else
   590             end else
   522                 begin
   591                 begin
   523                 {if not scoreShown then
   592 {$IFDEF DEBUGAI}
       
   593                 if not scoreShown then
   524                     begin
   594                     begin
   525                     if BestActions.Score > 0 then ParseCommand('/say Expected score = ' + inttostr(BestActions.Score div 1024), true);
   595                     if BestActions.Score > 0 then ParseCommand('/say Expected score = ' + inttostr(BestActions.Score div 1024), true);
   526                     scoreShown:= true
   596                     scoreShown:= true
   527                     end;}
   597                     end;
       
   598 {$ENDIF}
   528                 ProcessAction(BestActions, Gear)
   599                 ProcessAction(BestActions, Gear)
   529                 end
   600                 end
   530         else if ((GameTicks - StartTicks) > cMaxAIThinkTime)
   601         else if ((GameTicks - StartTicks) > cMaxAIThinkTime)
   531             or (TurnTimeLeft <= cStopThinkTime) then
   602             or (TurnTimeLeft <= cStopThinkTime) then
   532                 StopThinking:= true
   603                 StopThinking:= true
   533 end;
   604 end;
   534 
   605 
   535 procedure initModule;
   606 procedure initModule;
   536 begin
   607 begin
   537     hasThread:= 0;
       
   538     StartTicks:= 0;
   608     StartTicks:= 0;
   539     ThinkThread:= ThinkThread;
   609     ThreadSem:= SDL_CreateSemaphore(1);
   540 end;
   610 end;
   541 
   611 
   542 procedure freeModule;
   612 procedure freeModule;
   543 begin
   613 begin
   544     FreeActionsList();
   614     FreeActionsList();
       
   615     SDL_DestroySemaphore(ThreadSem);
   545 end;
   616 end;
   546 
   617 
   547 end.
   618 end.