# HG changeset patch # User alfadur # Date 1592698101 -10800 # Node ID 6c689729b7458263532c370591511e06964f2025 # Parent 237d691e5069c65931e15be456a1e283c1fa8053# Parent 92ce801d0681a36749d6cdd06fe1940186a74a4d merge blowtorch draft diff -r 237d691e5069 -r 6c689729b745 ChangeLog.txt --- a/ChangeLog.txt Sun Jun 21 03:00:39 2020 +0300 +++ b/ChangeLog.txt Sun Jun 21 03:08:21 2020 +0300 @@ -10,11 +10,19 @@ + Themes: Add fade-in and fade-out effects + Themes: Make Sudden Death flakes in Underwater theme rise + New taunt chat commands: /bubble, /happy + + Teach computer players how to ... + + - use drill strike, piano strike, air mine, cleaver, seduction + + - use mine strike (0 seconds only) + + - use RC plane (very basic) + + - drop mines from a cliff + + Low level computer players are more inaccurate with guns + + Various small computer player improvements * Racer: Resize waypoints in custom-sized drawn maps * Mutant: Fix impossible to become mutant after mutant is gone * A Classic Fairytale: Mission 1: Fix possibility of getting stuck in “Leap of Faith” section * A Space Adventure: The First Stop: Fix broken victory condition when eliminating minions * A Space Adventure: Killing the Specialists: Don't award player health if enemy hurts itself + * Fix many projectiles not being affected by Heavy Wind after turn end * Fix hog getting stuck when opening parachute right after a shoryuken digging through land * Fix weapon schemes sometimes not being saved properly diff -r 237d691e5069 -r 6c689729b745 hedgewars/uAI.pas --- a/hedgewars/uAI.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uAI.pas Sun Jun 21 03:08:21 2020 +0300 @@ -167,11 +167,6 @@ AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0); - if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then - begin - AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY) - end; - if (ap.Angle > 0) then AddAction(BestActions, aia_LookRight, 0, 200, 0, 0) else if (ap.Angle < 0) then @@ -180,6 +175,18 @@ if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0); + if ((Ammoz[a].Ammo.Propz and ammoprop_SetBounce) > 0) and (ap.Bounce > 0) then + begin + AddAction(BestActions, aia_Precise, aim_push, 10, 0, 0); + AddAction(BestActions, aia_Timer, ap.Bounce, 200, 0, 0); + AddAction(BestActions, aia_Precise, aim_release, 10, 0, 0); + end; + + if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then + begin + AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY) + end; + if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then begin dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle); @@ -247,7 +254,7 @@ const FallPixForBranching = cHHRadius; var maxticks, oldticks, steps, tmp: Longword; - BaseRate, BestRate, Rate: LongInt; + BaseRate, BestRate, Rate, i: LongInt; GoInfo: TGoInfo; CanGo: boolean; AltMe: TGear; @@ -321,6 +328,13 @@ AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0); AddAction(BestActions, aia_attack, aim_push, 10, 0, 0); AddAction(BestActions, aia_attack, aim_release, 10, 0, 0); + // Better bot levels know they can spam extra time if infinite + if (BotLevel < 3) and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) = AMMO_INFINITE) then + for i:= 1 to 3 do + begin + AddAction(BestActions, aia_attack, aim_push, 100, 0, 0); + AddAction(BestActions, aia_attack, aim_release, 100, 0, 0); + end; end; break; diff -r 237d691e5069 -r 6c689729b745 hedgewars/uAIActions.pas --- a/hedgewars/uAIActions.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uAIActions.pas Sun Jun 21 03:08:21 2020 +0300 @@ -31,6 +31,7 @@ aia_Up = 5; aia_Down = 6; aia_Switch = 7; + aia_Precise = 8; aia_Weapon = $8000; aia_WaitXL = $8001; @@ -73,7 +74,7 @@ var PrevX: LongInt = 0; timedelta: Longword = 0; -const ActionIdToStr: array[0..7] of string[16] = ( +const ActionIdToStr: array[0..8] of string[16] = ( {aia_none} '', {aia_Left} 'left', {aia_Right} 'right', @@ -81,7 +82,8 @@ {aia_attack} 'attack', {aia_Up} 'up', {aia_Down} 'down', -{aia_Switch} 'switch' +{aia_Switch} 'switch', +{aia_Precise} 'precise' ); {$IFDEF TRACEAIACTIONS} diff -r 237d691e5069 -r 6c689729b745 hedgewars/uAIAmmoTests.pas --- a/hedgewars/uAIAmmoTests.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uAIAmmoTests.pas Sun Jun 21 03:08:21 2020 +0300 @@ -30,7 +30,7 @@ var windSpeed: real; type TAttackParams = record - Time, AttacksNum: Longword; + Time, Bounce, AttacksNum: Longword; Angle, Power: LongInt; ExplX, ExplY, ExplR: LongInt; AttackPutX, AttackPutY: LongInt; @@ -44,6 +44,7 @@ function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; @@ -53,10 +54,19 @@ function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; type TAmmoTestProc = function (Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; TAmmoTest = record @@ -75,7 +85,7 @@ (proc: nil; flags: 0), // amPickHammer (proc: nil; flags: 0), // amSkip (proc: nil; flags: 0), // amRope - (proc: nil; flags: 0), // amMine + (proc: @TestMine; flags: amtest_NoTarget), // amMine (proc: @TestDesertEagle; flags: amtest_MultipleAttacks), // amDEagle (proc: @TestDynamite; flags: amtest_NoTarget), // amDynamite (proc: @TestFirePunch; flags: amtest_NoTarget), // amFirePunch @@ -83,7 +93,7 @@ (proc: @TestBaseballBat; flags: amtest_NoTarget), // amBaseballBat (proc: nil; flags: 0), // amParachute (proc: @TestAirAttack; flags: amtest_Rare), // amAirAttack - (proc: nil; flags: 0), // amMineStrike + (proc: @TestMineStrike; flags: amtest_Rare), // amMineStrike (proc: nil; flags: 0), // amBlowTorch (proc: nil; flags: 0), // amGirder (proc: nil; flags: 0), // amTeleport @@ -92,13 +102,13 @@ (proc: @TestMortar; flags: 0), // amMortar (proc: @TestKamikaze; flags: 0), // amKamikaze (proc: @TestCake; flags: amtest_Rare or amtest_NoTarget), // amCake - (proc: nil; flags: 0), // amSeduction + (proc: @TestSeduction; flags: amtest_NoTarget), // amSeduction (proc: @TestWatermelon; flags: 0), // amWatermelon (proc: nil; flags: 0), // amHellishBomb (proc: nil; flags: 0), // amNapalm (proc: @TestDrillRocket; flags: 0), // amDrill (proc: nil; flags: 0), // amBallgun - (proc: nil; flags: 0), // amRCPlane + (proc: @TestRCPlane; flags: 0), // amRCPlane (proc: nil; flags: 0), // amLowGravity (proc: nil; flags: 0), // amExtraDamage (proc: nil; flags: 0), // amInvulnerable @@ -110,23 +120,23 @@ (proc: @TestMolotov; flags: 0), // amMolotov (proc: nil; flags: 0), // amBirdy (proc: nil; flags: 0), // amPortalGun - (proc: nil; flags: 0), // amPiano + (proc: @TestPiano; flags: amtest_Rare), // amPiano (proc: @TestGrenade; flags: amtest_NoTrackFall), // amGasBomb (proc: @TestShotgun; flags: 0), // amSineGun (proc: nil; flags: 0), // amFlamethrower - (proc: @TestGrenade; flags: 0), // amSMine + (proc: @TestSMine; flags: 0), // amSMine (proc: @TestHammer; flags: amtest_NoTarget), // amHammer (proc: nil; flags: 0), // amResurrector - (proc: nil; flags: 0), // amDrillStrike + (proc: @TestDrillStrike; flags: amtest_Rare), // amDrillStrike (proc: nil; flags: 0), // amSnowball (proc: nil; flags: 0), // amTardis (proc: nil; flags: 0), // amLandGun (proc: nil; flags: 0), // amIceGun - (proc: nil; flags: 0), // amKnife + (proc: @TestKnife; flags: 0), // amKnife (proc: nil; flags: 0), // amRubber - (proc: nil; flags: 0), // amAirMine + (proc: @TestAirMine; flags: 0), // amAirMine (proc: nil; flags: 0), // amCreeper - (proc: @TestShotgun; flags: 0) // amMinigun + (proc: @TestMinigun; flags: 0) // amMinigun ); implementation @@ -411,6 +421,87 @@ TestDrillRocket:= valueResult end; +function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MIN_RANGE = 200; +var Vx, Vy, meX, meY, x, y: real; + rx, ry, valueResult: LongInt; + range, maxRange: integer; +begin +// This is a very simple test to let a RC plane fly in a straight line, without dropping any bombs +// TODO: Teach AI how to steer +// TODO: Teach AI how to drop bombs +// TODO: Teach AI how to predict fire behavior +Flags:= Flags; // avoid compiler hint +if Level = 5 then + exit(BadTurn) +else if Level = 4 then + maxRange:= 2200 +else if Level = 3 then + maxRange:= 2900 +else if Level = 2 then + maxRange:= 3500 +else + maxRange:= 3900; +TestRCPlane:= BadTurn; +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +x:= meX; +y:= meY; +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range < MIN_RANGE ) or ( range > maxRange) then + exit(BadTurn); + +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + + // Intentionally low rating to discourage use + if Level = 1 then + valueResult:= RateExplosion(Me, rx, ry, 26, afTrackFall or afErasesLand) + else + valueResult:= RateExplosion(Me, rx, ry, 26); + + // Check range again in case the plane collided before target + range:= Metric(trunc(meX), trunc(meY), rx, ry); + if ( range < MIN_RANGE ) or ( range > maxRange) then + exit(BadTurn); + + // If impact location is close, above us and wind blows in our direction, + // there's a risk of fire flying towards us, so fail in this case. + if (Level < 3) and (range <= 600) and (meY >= ry) and + (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then + exit(BadTurn); + + // Apply inaccuracy + if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 9))); + + if (valueResult <= 0) then + valueResult:= BadTurn; + exit(valueResult) + end +until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestRCPlane:= BadTurn +end; function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var Vx, Vy, r: real; @@ -479,61 +570,85 @@ end; function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; -var Vx, Vy, r: real; - Score, EX, EY, valueResult: LongInt; - TestTime: LongInt; - targXWrap, x, y, dY, meX, meY: real; +const timeLimit = 50; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; t: LongInt; + value, range: LongInt; begin Flags:= Flags; // avoid compiler hint meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); -valueResult:= BadTurn; -TestTime:= 0; +ap.Time:= 0; +rTime:= 350; ap.ExplR:= 0; if (WorldEdge = weWrap) then if (Targ.Point.X < meX) then targXWrap:= Targ.Point.X + (RightX-LeftX) else targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; repeat - inc(TestTime, 300); + rTime:= rTime + 300 + Level * 50 + random(300); if (WorldEdge = weWrap) and (random(2)=0) then - Vx:= (targXWrap - meX) / TestTime - else Vx:= (Targ.Point.X - meX) / TestTime; - Vy:= cGravityf * (TestTime div 2) - Targ.Point.Y - meY / TestTime; + Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime + else + Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; r:= sqr(Vx) + sqr(Vy); + if not (r > 1) then begin x:= meX; y:= meY; + dX:= Vx; dY:= -Vy; - t:= TestTime; + t:= rTime; repeat x:= CheckWrap(x); - x:= x + Vx; + x:= x + dX; + y:= y + dY; dY:= dY + cGravityf; dec(t) - until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 6)) or - ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 6))) or (t = 0); + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 5)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 5))) or (t < -timeLimit); + EX:= trunc(x); EY:= trunc(y); - if t < 50 then - Score:= RateExplosion(Me, EX, EY, 97) // average of 17 attempts, most good, but some failing spectacularly - else - Score:= BadTurn; + + // Sanity check 1: Make sure we're not too close to impact location + range:= Metric(trunc(meX), trunc(meY), EX, EY); + if (range < 150) and (Level < 5) then + exit(BadTurn); + + // Sanity check 2: If impact location is close, above us and wind blows + // towards us, there's a risk of fire flying towards us, so fail in this case. + if (Level < 3) and (range <= 600) and (trunc(meY) >= EX) and + (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then + exit(BadTurn); - if valueResult < Score then + if t >= -timeLimit then + value:= RateExplosion(Me, EX, EY, 97) // average of 17 attempts, most good, but some failing spectacularly + else + value:= BadTurn; + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level < 3)) then begin - ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random(Level)); - ap.Power:= trunc(sqrt(r) * cMaxPower) + AIrndSign(random(Level) * 15); + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1); ap.ExplR:= 100; ap.ExplX:= EX; ap.ExplY:= EY; - valueResult:= Score + valueResult:= value end; end -until (TestTime > 5050 - Level * 800); +until rTime > 5050 - Level * 800; TestMolotov:= valueResult end; @@ -547,6 +662,7 @@ begin valueResult:= BadTurn; TestTime:= 0; +ap.Bounce:= 0; ap.ExplR:= 0; meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); @@ -611,6 +727,7 @@ Flags:= Flags; // avoid compiler hint valueResult:= BadTurn; TestTime:= 500; +ap.Bounce:= 0; ap.ExplR:= 0; meX:= hwFloat2Float(Me^.X); meY:= hwFloat2Float(Me^.Y); @@ -827,6 +944,9 @@ Vx:= (Targ.Point.X - x) * 1 / 1024; Vy:= (Targ.Point.Y - y) * 1 / 1024; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 10))); repeat x:= x + vX; y:= y + vY; @@ -881,6 +1001,9 @@ Vx:= (Targ.Point.X - x) * t; Vy:= (Targ.Point.Y - y) * t; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 10))); d:= 0; ix:= trunc(x); @@ -935,6 +1058,9 @@ Vx:= (Targ.Point.X - x) * t; Vy:= (Targ.Point.Y - y) * t; ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Apply inaccuracy +inc(ap.Angle, + AIrndSign(random((Level - 1) * 5))); + d:= 0; repeat @@ -1160,7 +1286,10 @@ dx:= (Targ.Point.X - x) * t; dy:= (Targ.Point.Y - y) * t; - ap.Angle:= DxDy2AttackAnglef(dx, -dy) + ap.Angle:= DxDy2AttackAnglef(dx, -dy); + // Apply inaccuracy + if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 10))); end; if dx >= 0 then cx:= 0.45 else cx:= -0.45; @@ -1173,7 +1302,7 @@ valueResult:= valueResult + RateShove(Me, trunc(x), trunc(y) , 30, 30, 25 - , cx, -0.9, trackFall or afSetSkip); + , cx, -0.9, trackFall or afSetSkip or afIgnoreMe); end; if (d < 10) and (dx = 0) then @@ -1183,14 +1312,14 @@ tx:= trunc(x); v:= RateShove(Me, tx, trunc(y) , 30, 30, 25 - , -cx, -0.9, trackFall); + , -cx, -0.9, trackFall or afIgnoreMe); for i:= 1 to 512 div step - 2 do begin y:= y + dy; v:= v + RateShove(Me, tx, trunc(y) , 30, 30, 25 - , -cx, -0.9, trackFall or afSetSkip); + , -cx, -0.9, trackFall or afSetSkip or afIgnoreMe); end end; @@ -1203,11 +1332,11 @@ v:= RateShove(Me, trunc(x), trunc(y) , 30, 30, 25 - , cx, -0.9, trackFall); + , cx, -0.9, trackFall or afIgnoreMe); valueResult:= valueResult + v - KillScore * friendlyfactor div 100 * 1024; if v < 65536 then - inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30)); + inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30, afIgnoreMe)); TestKamikaze:= valueResult; end; @@ -1303,6 +1432,361 @@ TestAirAttack:= valueResult; end; +function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const cShift = 4; +var bombsSpeed, X, Y, dX, dY, drillX, drillY: real; + t2: real; + b: array[0..9] of boolean; + dmg: array[0..9] of LongInt; + fexit, collided: boolean; + i, t, value, valueResult, attackTime, drillTimer, targetX: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +if (Level > 3) or (cGravityf = 0) then + exit(BadTurn); + +ap.Angle:= 0; +targetX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +bombsSpeed:= hwFloat2Float(cBombsSpeed); +X:= Targ.Point.X - 135 - cShift; // hh center - cShift +X:= X - bombsSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf); +Y:= -128; +dX:= bombsSpeed; +dY:= 0; + +valueResult:= 0; + +attackTime:= 0; +while attackTime <= 4000 do + begin + inc(attackTime, 1000); + value:= 0; + for i:= 0 to 9 do + begin + b[i]:= true; + dmg[i]:= 0 + end; + + repeat + X:= X + dX; + Y:= Y + dY; + dY:= dY + cGravityf; + fexit:= true; + + for i:= 0 to 9 do + if b[i] then + begin + fexit:= false; + collided:= false; + drillX:= trunc(X) + LongWord(i * 30); + drillY:= trunc(Y); + // Collided with land ... simulate drilling + if TestCollExcludingObjects(trunc(drillX), trunc(drillY), 4) and + (Abs(Targ.Point.X - trunc(X)) + Abs(Targ.Point.Y - trunc(Y)) > 21) then + begin + drillTimer := attackTime; + t2 := 0.5 / sqrt(sqr(dX) + sqr(dY)); + dX := dX * t2; + dY := dY * t2; + repeat + drillX:= drillX + dX; + drillY:= drillY + dY; + dec(drillTimer, 10); + until (Abs(Targ.Point.X - drillX) + Abs(Targ.Point.Y - drillY) < 22) + or (drillX < 0) + or (drillY < 0) + or (trunc(drillX) > LAND_WIDTH) + or (trunc(drillY) > LAND_HEIGHT) + // TODO: Simulate falling again when rocket has left terrain again + or (drillTimer <= 0); + collided:= true; + end + // Collided with something else ... record collision + else if TestColl(trunc(drillX), trunc(drillY), 4) then + collided:= true; + + // Simulate explosion + if collided then + begin + b[i]:= false; + dmg[i]:= RateExplosion(Me, trunc(drillX), trunc(drillY), 58); + // 58 (instead of 60) for better prediction (hh moves after explosion of one of the rockets) + end; + end; + until fexit or (Y > cWaterLine); + + for i:= 0 to 5 do + if dmg[i] <> BadTurn then + inc(value, dmg[i]); + t:= value; + targetX:= Targ.Point.X - 60; + + for i:= 0 to 3 do + if dmg[i] <> BadTurn then + begin + dec(t, dmg[i]); + inc(t, dmg[i + 6]); + if t > value then + begin + value:= t; + targetX:= Targ.Point.X - 30 - cShift + i * 30 + end + end; + + if value > valueResult then + begin + valueResult:= value; + ap.AttackPutX:= targetX; + ap.Time:= attackTime; + end; +end; + +if valueResult <= 0 then + valueResult:= BadTurn +else + begin + // Weaker AI has chance to get the time wrong by 1-3 seconds + if Level = 5 then + // +/- 3 seconds + ap.Time:= ap.Time + (3 - random(7)) * 1000 + else if Level = 4 then + // +/- 2 seconds + ap.Time:= ap.Time + (2 - random(5)) * 1000 + else if Level = 3 then + // +/- 1 second + if (random(2) = 0) then + ap.Time:= ap.Time + (1 - random(3)) * 1000 + else if Level = 2 then + // 50% chance for +/- 1 second + if (random(2) = 0) then + ap.Time:= ap.Time + (1 - random(3)) * 1000; + ap.Time:= Min(5000, Max(1000, ap.Time)); + end; + +TestDrillStrike:= valueResult; +end; + +function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const cShift = 4; +var minesSpeed, X, Y, dY: real; + b: array[0..9] of boolean; + dmg: array[0..9] of LongInt; + fexit: boolean; + i, t, valueResult: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +ap.Time:= 0; + +// AI currently only supports cMinesTime = 0 because it's the most +// predictable. +// Other cMinesTime values are risky because of bouncy mines; +// so they are unsupported. +// TODO: Implement mine strike for other values of MineTime +// TODO: Teach AI to avoid hitting their own with mines +if (Level > 3) or (cGravityf = 0) or (cMinesTime <> 0) then + exit(BadTurn); + +ap.Angle:= 0; +ap.AttackPutX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +minesSpeed:= hwFloat2Float(cBombsSpeed); +X:= Targ.Point.X - 135 - cShift; // hh center - cShift +X:= X - minesSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf); +Y:= -128; +dY:= 0; + +for i:= 0 to 9 do + begin + b[i]:= true; + dmg[i]:= 0 + end; +valueResult:= 0; + +repeat + X:= X + minesSpeed; + Y:= Y + dY; + dY:= dY + cGravityf; + fexit:= true; + + for i:= 0 to 9 do + if b[i] then + begin + fexit:= false; + if TestColl(trunc(X) + LongWord(i * 30), trunc(Y), 4) then + begin + b[i]:= false; + dmg[i]:= RateExplosion(Me, trunc(X) + LongWord(i * 30), trunc(Y), 96) + end + end; +until fexit or (Y > cWaterLine); + +for i:= 0 to 5 do + if dmg[i] <> BadTurn then + inc(valueResult, dmg[i]); +t:= valueResult; +ap.AttackPutX:= Targ.Point.X - 60; + +for i:= 0 to 3 do + if dmg[i] <> BadTurn then + begin + dec(t, dmg[i]); + inc(t, dmg[i + 6]); + if t > valueResult then + begin + valueResult:= t; + ap.AttackPutX:= Targ.Point.X - 30 - cShift + i * 30 + end + end; + +if valueResult <= 0 then + valueResult:= BadTurn; +TestMineStrike:= valueResult; +end; + +function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const timeLimit = 50; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; + t: LongInt; + value: LongInt; +begin +Flags:= Flags; // avoid compiler hint +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +ap.Time:= 0; +rTime:= 350; +ap.ExplR:= 0; +if (WorldEdge = weWrap) then + if (Targ.Point.X < meX) then + targXWrap:= Targ.Point.X + (RightX-LeftX) + else targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; +repeat + rTime:= rTime + 300 + Level * 50 + random(300); + if (WorldEdge = weWrap) and (random(2)=0) then + Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime + else + Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; + r:= sqr(Vx) + sqr(Vy); + + if not (r > 1) then + begin + x:= meX; + y:= meY; + dX:= Vx; + dY:= -Vy; + t:= rTime; + repeat + x:= CheckWrap(x); + x:= x + dX; + + y:= y + dY; + dY:= dY + cGravityf; + dec(t) + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2))) or (t < -timeLimit); + + EX:= trunc(x); + EY:= trunc(y); + + if t >= -timeLimit then + if (Level = 1) and (Flags and amtest_NoTrackFall = 0) then + value:= RateExplosion(Me, EX, EY, 61, afTrackFall) + else + value:= RateExplosion(Me, EX, EY, 61); + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level < 3)) then + begin + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1); + ap.ExplR:= 60; + ap.ExplX:= EX; + ap.ExplY:= EY; + valueResult:= value + end; + end +until rTime > 5050 - Level * 800; +TestSMine:= valueResult +end; + +function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const BOUNCES = 5; +var X, Y: real; + dmg: array[0..BOUNCES-1] of LongInt; + i, e, rate, valueResult: LongInt; +begin +Flags:= Flags; // avoid compiler hint +ap.ExplR:= 0; +ap.Time:= 0; +if (cGravityf <= 0) then + exit(BadTurn); + +if (Level > 2) then + exit(BadTurn); + +ap.Angle:= 0; +ap.AttackPutX:= Targ.Point.X; +ap.AttackPutY:= Targ.Point.Y; + +X:= Targ.Point.X; +Y:= -128; + +for i:= 0 to BOUNCES-1 do + dmg[i]:= 0; + +i:= 1; +repeat + // Piano goes down + Y:= Y + 11; + if TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(X), trunc(Y), 32) then + begin + for e:= -1 to 1 do + begin + rate:= RateExplosion(Me, trunc(X) + 30*e, trunc(Y)+40, 161, afIgnoreMe); + if rate <> BadTurn then + dmg[i]:= dmg[i] + rate; + end; + + if (i > 1) and (dmg[i] > 0) then + dmg[i]:= dmg[i] div 2; + inc(i); + // Skip past the blast hole + Y:= Y + 41 + end; +until (i > BOUNCES) or (Y > cWaterLine); + +if (i = 0) and (Y > cWaterLine) then + exit(BadTurn); + +valueResult:= 0; +for i:= 0 to BOUNCES do + if dmg[i] <= BadTurn then + begin + valueResult:= BadTurn; + break; + end + else + inc(valueResult, dmg[i]); +ap.AttackPutX:= Targ.Point.X; + +valueResult:= valueResult - KillScore * friendlyfactor div 100 * 1024; + +if valueResult <= 0 then + valueResult:= BadTurn; +TestPiano:= valueResult; +end; function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var @@ -1426,6 +1910,27 @@ TestCake:= valueResult; end; +function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +var rate: LongInt; +begin +Flags:= Flags; // avoid compiler hint +Level:= Level; // avoid compiler hint +Targ:= Targ; + +if (Level = 5) then + exit(BadTurn); + +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +ap.Angle:= 0; + +rate:= RateSeduction(Me); +if rate <= 0 then + rate:= BadTurn; +TestSeduction:= rate; +end; + function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; var valueResult: LongInt; x, y, dx, dy: real; @@ -1471,4 +1976,263 @@ TestDynamite:= valueResult end; +function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +var valueResult: LongInt; + x, y, dx, dy: real; + EX, EY, t: LongInt; +begin +Flags:= Flags; // avoid compiler hint +Targ:= Targ; // avoid compiler hint + +x:= hwFloat2Float(Me^.X) + hwSign(Me^.dX) * 7; +y:= hwFloat2Float(Me^.Y); +dx:= hwSign(Me^.dX) * 0.02; +dy:= 0; +t:= 10000; +repeat + dec(t); + x:= x + dx; + dy:= dy + cGravityf; + y:= y + dy; + if ((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2)) then + t:= 0; +until t = 0; + +EX:= trunc(x); +EY:= trunc(y); + +if Level = 1 then + valueResult:= RateExplosion(Me, EX, EY, 51, afTrackFall or afErasesLand) +else + valueResult:= RateExplosion(Me, EX, EY, 51); + +if (valueResult > 0) then + begin + ap.Angle:= 0; + ap.Power:= 1; + ap.Time:= 0; + if (Level < 5) then + // Set minimum mine bounciness for improved aim + ap.Bounce:= 1 + else + ap.Bounce:= 0; + ap.ExplR:= 100; + ap.ExplX:= EX; + ap.ExplY:= EY + end else + valueResult:= BadTurn; + +TestMine:= valueResult +end; + +function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const timeLimit = 300; +var Vx, Vy, r, meX, meY: real; + rTime: LongInt; + EX, EY: LongInt; + valueResult: LongInt; + targXWrap, x, y, dX, dY: real; + t: LongInt; + value, range: LongInt; +begin +Flags:= Flags; // avoid compiler hint +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +ap.Time:= 0; +rTime:= 350; +ap.ExplR:= 0; +if (WorldEdge = weWrap) then + if (Targ.Point.X < meX) then + targXWrap:= Targ.Point.X + (RightX-LeftX) + else + targXWrap:= Targ.Point.X - (RightX-LeftX); +valueResult:= BadTurn; +repeat + rTime:= rTime + 300 + Level * 50 + random(300); + if (WorldEdge = weWrap) and (random(2)=0) then + Vx:= (targXWrap - meX) / rTime + else + Vx:= (Targ.Point.X - meX) / rTime; + Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime; + r:= sqr(Vx) + sqr(Vy); + + if not (r > 1) then + begin + x:= meX; + y:= meY; + dX:= Vx; + dY:= -Vy; + t:= rTime; + repeat + x:= CheckWrap(x); + x:= x + dX; + + y:= y + dY; + dY:= dY + cGravityf; + dec(t) + until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 7)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 7))) or (t < -timeLimit); + + EX:= trunc(x); + EY:= trunc(y); + + // Sanity check: Make sure we're not too close to impact location + range:= Metric(trunc(meX), trunc(meY), EX, EY); + if (range <= 40) then + exit(BadTurn); + + if t >= -timeLimit then + value:= RateShove(Me, EX, EY, 16, trunc(sqr((abs(dY)+abs(dX))*40000/10000)), 0, dX, dY, 0) + else + value:= BadTurn; + + if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + value := BadTurn; + + if (valueResult < value) or ((valueResult = value) and (Level = 1)) then + begin + ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 12)); + ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 22 + 1); + valueResult:= value + end; + end +until rTime > 5050 - Level * 800; +TestKnife:= valueResult +end; + +function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MIN_RANGE = 160; + MAX_RANGE = 2612; +var Vx, Vy, meX, meY, x, y, r: real; + rx, ry, valueResult: LongInt; + range, maxRange: integer; +begin +Flags:= Flags; // avoid compiler hint +maxRange:= MAX_RANGE - ((Level - 1) * 300); +TestAirMine:= BadTurn; +ap.ExplR:= 60; +ap.Time:= 0; +meX:= hwFloat2Float(Me^.X); +meY:= hwFloat2Float(Me^.Y); +x:= meX; +y:= meY; + +// Rough first range check +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range < MIN_RANGE ) or ( range > maxRange ) then + exit(BadTurn); + +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + + if Level = 1 then + valueResult:= RateExplosion(Me, rx, ry, 61, afTrackFall) + else + valueResult:= RateExplosion(Me, rx, ry, 61); + + // Precise range calculation required to calculate power; + // The air mine is very sensitive to small changes in power. + r:= sqr(meX - rx) + sqr(meY - ry); + range:= trunc(sqrt(r)); + + if ( range < MIN_RANGE ) or ( range > maxRange ) then + exit(BadTurn); + ap.Power:= ((range + cHHRadius*2) * cMaxPower) div MAX_RANGE; + + // Apply inaccuracy + inc(ap.Power, (random(93*(Level-1)) - 31*(Level-1))); // Level 1 spread: -124 .. 248 + if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 10))); + + if (valueResult <= 0) then + valueResult:= BadTurn; + exit(valueResult) + end +until (abs(Targ.Point.X - trunc(x)) + abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestAirMine := BadTurn +end; + +function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt; +const + MAX_RANGE = 400; +var Vx, Vy, x, y: real; + rx, ry, valueResult: LongInt; + range: integer; +begin +// This code is still very similar to TestShotgun, +// but it's a good simple estimate. +// TODO: Simulate random bullets +// TODO: Replace RateShotgun with something else +// TODO: Teach AI to move aim during shooting +Flags:= Flags; // avoid compiler hint +TestMinigun:= BadTurn; +ap.ExplR:= 0; +ap.Time:= 0; +ap.Power:= 1; +x:= hwFloat2Float(Me^.X); +y:= hwFloat2Float(Me^.Y); +range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y); +if ( range > MAX_RANGE ) then + exit(BadTurn); + +Vx:= (Targ.Point.X - x) * 1 / 1024; +Vy:= (Targ.Point.Y - y) * 1 / 1024; +ap.Angle:= DxDy2AttackAnglef(Vx, -Vy); +// Minigun angle is limited +if (ap.Angle < Ammoz[amMinigun].minAngle) or (ap.Angle > Ammoz[amMinigun].maxAngle) then + exit(BadTurn); + +// Apply inaccuracy +if (not cLaserSighting) then + inc(ap.Angle, + AIrndSign(random((Level - 1) * 10))); +repeat + x:= x + vX; + y:= y + vY; + rx:= trunc(x); + ry:= trunc(y); + if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 1)) or + ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 1)) then + begin + x:= x + vX * 8; + y:= y + vY * 8; + // TODO: Use different rating function + valueResult:= RateShotgun(Me, vX, vY, rx, ry); + + if (valueResult = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then + begin + if GameFlags and gfSolidLand = 0 then + valueResult:= 1024 - Metric(Targ.Point.X, Targ.Point.Y, rx, ry) div 64 + else valueResult := BadTurn + end + else + dec(valueResult, Level * 4000); + exit(valueResult) + end +until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4) + or (x < 0) + or (y < 0) + or (trunc(x) > LAND_WIDTH) + or (trunc(y) > LAND_HEIGHT); + +TestMinigun:= BadTurn +end; + end. diff -r 237d691e5069 -r 6c689729b745 hedgewars/uAIMisc.pas --- a/hedgewars/uAIMisc.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uAIMisc.pas Sun Jun 21 03:08:21 2020 +0300 @@ -27,6 +27,7 @@ afTrackFall = $00000001; afErasesLand = $00000002; afSetSkip = $00000004; + afIgnoreMe = $00000008; BadTurn = Low(LongInt) div 4; @@ -86,6 +87,7 @@ function RealRateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt; function RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt; function RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt; +function RateSeduction(Me: PGear): LongInt; function RateHammer(Me: PGear): LongInt; function HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean; @@ -539,20 +541,22 @@ x:= round(CheckWrap(real(x))); fallDmg:= 0; rate:= 0; -// add our virtual position -with Targets.ar[Targets.Count] do - begin - Point.x:= hwRound(Me^.X); - Point.y:= hwRound(Me^.Y); - skip:= false; - matters:= true; - Kind:= gtHedgehog; - Density:= 1; - Radius:= cHHRadius; - Score:= - ThinkingHH^.Health - end; + +if (Flags and afIgnoreMe) = 0 then + // add our virtual position + with Targets.ar[Targets.Count] do + begin + Point.x:= hwRound(Me^.X); + Point.y:= hwRound(Me^.Y); + skip:= false; + matters:= true; + Kind:= gtHedgehog; + Density:= 1; + Radius:= cHHRadius; + Score:= - ThinkingHH^.Health + end; + // rate explosion - if (Flags and afErasesLand <> 0) and (GameFlags and gfSolidLand = 0) then erasure:= r else erasure:= 0; @@ -561,8 +565,9 @@ for i:= 0 to Targets.Count do if not Targets.ar[i].dead then with Targets.ar[i] do - if not matters then hadSkips:= true - else + if not matters then + hadSkips:= true + else begin dmg:= 0; dmgBase:= r + Radius div 2; @@ -832,6 +837,92 @@ ResetTargets; end; +function RateSeduction(Me: PGear): LongInt; +var pX, pY, i, r, rate, subrate, fallDmg: LongInt; + diffX, diffY: LongInt; + meX, meY, dX, dY: hwFloat; + pXr, pYr: real; + hadSkips: boolean; +begin +meX:= Me^.X; +meY:= Me^.Y; +rate:= 0; +for i:= 0 to Targets.Count do + if not Targets.ar[i].dead then + with Targets.ar[i] do + begin + pX:= Point.X; + pY:= Point.Y; + diffX:= pX - hwRound(meX); + diffY:= pY - hwRound(meY); + if (Me^.Hedgehog^.BotLevel < 4) and (abs(diffX) <= cHHRadius*2) and (diffY >= 0) and (diffY <= cHHRadius*2) then + // Don't use seduction if too close to other hog. We could be + // standing on it, so using seduction would remove the ground on + // which we stand on, which is dangerous + exit(BadTurn); + + if (not matters) then + hadSkips:= true + else if matters and (Kind = gtHedgehog) and (abs(pX - hwRound(meX)) + abs(pY - hwRound(meY)) < cSeductionDist) then + begin + r:= trunc(sqrt(sqr(abs(pX - hwRound(meX)))+sqr(abs(pY - hwRound(meY))))); + if r < cSeductionDist then + begin + + if (WorldEdge <> weWrap) or (not (hwAbs(meX - int2hwFloat(pX)) > int2hwFloat(cSeductionDist))) then + dX:= _50 * cGravity * (meX - int2hwFloat(pX)) / _25 + else if (not (hwAbs(meX + int2hwFloat((RightX-LeftX) - pX)) > int2hwFloat(cSeductionDist))) then + dX:= _50 * cGravity * (meX + (int2hwFloat((RightX-LeftX) - pX))) / _25 + else + dX:= _50 * cGravity * (meX - (int2hwFloat((RightX-LeftX) - pX))) / _25; + dY:= -_450 * cMaxWindSpeed * 2; + + + pXr:= pX; + pYr:= pY; + fallDmg:= trunc(TraceShoveFall(pXr, pYr, hwFloat2Float(dX), hwFloat2Float(dY), Targets.ar[i]) * dmgMod); + + // rate damage + if fallDmg < 0 then // drowning + begin + if Score > 0 then + inc(rate, (KillScore + Score div 10) * 1024) // Add a bit of a bonus for bigger hog drownings + else + dec(rate, (KillScore * friendlyfactor div 100 - Score div 10) * 1024) // and more of a punishment for drowning bigger friendly hogs + end + else if (fallDmg) >= abs(Score) then // deadly fall damage + begin + dead:= true; + Targets.reset:= true; + if (hwFloat2Float(dX) < 0.035) then + begin + subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or afTrackFall); // hog explodes + if abs(subrate) > 2000 then + inc(rate, subrate) + end; + if Score > 0 then + inc(rate, KillScore * 1024 + (fallDmg)) // tiny bonus for dealing more damage than needed to kill + else + dec(rate, KillScore * friendlyfactor div 100 * 1024) + end + else if (fallDmg <> 0) then // non-deadly fall damage + if Score > 0 then + inc(rate, fallDmg * 1024) + else + dec(rate, fallDmg * friendlyfactor div 100 * 1024) + else // no damage, just shoved + if (Score < 0) then + dec(rate, 100); // small penalty for shoving friendly hogs as it might be dangerous + end; + end; + end; + +if hadSkips and (rate <= 0) then + RateSeduction:= BadTurn +else + RateSeduction:= rate * 1024; +end; + function RateHammer(Me: PGear): LongInt; var x, y, i, r, rate: LongInt; hadSkips: boolean; diff -r 237d691e5069 -r 6c689729b745 hedgewars/uConsts.pas --- a/hedgewars/uConsts.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uConsts.pas Sun Jun 21 03:08:21 2020 +0300 @@ -228,6 +228,7 @@ cSeductionDist = 250; // effect distance of seduction ExtraTime = 30000; // amount of time (ms) given for using Extra Time + MaxMoreWindTime = 5000; // amount of time (ms) for land objects like gfMine to be affected after end of turn // do not change this value cDefaultZoomLevel = 2.0; // 100% zoom diff -r 237d691e5069 -r 6c689729b745 hedgewars/uGears.pas --- a/hedgewars/uGears.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uGears.pas Sun Jun 21 03:08:21 2020 +0300 @@ -595,6 +595,11 @@ dec(TurnTimeLeft) end; +if (TurnTimeLeft = 0) and (ReadyTimeLeft = 0) then + inc(TimeNotInTurn) +else + TimeNotInTurn:= 0; + if skipFlag then begin if TagTurnTimeLeft = 0 then diff -r 237d691e5069 -r 6c689729b745 hedgewars/uGearsHandlersMess.pas --- a/hedgewars/uGearsHandlersMess.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uGearsHandlersMess.pas Sun Jun 21 03:08:21 2020 +0300 @@ -535,8 +535,15 @@ if isFalling and (Gear^.State and gstNoGravity = 0) then begin + // Apply gravity and wind Gear^.dY := Gear^.dY + cGravity; - if (GameFlags and gfMoreWind <> 0) and (TurnTimeLeft > 0) and + if ((GameFlags and gfMoreWind) <> 0) and + // Disable gfMoreWind for land objects on turn end to prevent bouncing them forever + // This solution is rather ugly, in that it will occassionally suddenly wind physics + // while a gear is moving, this can be rather confusing. + // TODO: Find a way to make gfMoreWind-affected land objects settle more reliably + // and quickler without touching wind itselvs + ((not (Gear^.Kind in [gtMine, gtAirMine, gtSMine, gtKnife, gtExplosives])) or (TimeNotInTurn < MaxMoreWindTime)) and ((xland or land) = 0) and ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) then Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density diff -r 237d691e5069 -r 6c689729b745 hedgewars/uGearsHedgehog.pas --- a/hedgewars/uGearsHedgehog.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uGearsHedgehog.pas Sun Jun 21 03:08:21 2020 +0300 @@ -529,7 +529,8 @@ amMineStrike, amDrillStrike, amRubber, amMinigun: CurAmmoGear:= newGear; end; - if CurAmmoType = amCake then FollowGear:= newGear; + if (CurAmmoType = amCake) or (CurAmmoType = amPiano) then + FollowGear:= newGear; if ((CurAmmoType = amMine) or (CurAmmoType = amSMine) or (CurAmmoType = amAirMine)) and (GameFlags and gfInfAttack <> 0) then newGear^.FlightTime:= GameTicks + min(TurnTimeLeft,1000) diff -r 237d691e5069 -r 6c689729b745 hedgewars/uVariables.pas --- a/hedgewars/uVariables.pas Sun Jun 21 03:00:39 2020 +0300 +++ b/hedgewars/uVariables.pas Sun Jun 21 03:08:21 2020 +0300 @@ -105,6 +105,7 @@ TurnClockActive : boolean; TagTurnTimeLeft : Longword; ReadyTimeLeft : Longword; + TimeNotInTurn : Longword; // Milliseconds that passed while no turn is active IsGetAwayTime : boolean; GameOver : boolean; cSuddenDTurns : LongInt; @@ -2898,6 +2899,7 @@ GameOver := false; TurnClockActive := true; TagTurnTimeLeft := 0; + TimeNotInTurn := 0; cSuddenDTurns := 15; LastSuddenDWarn := -2; cInitHealth := 100; diff -r 237d691e5069 -r 6c689729b745 misc/flags_js.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/flags_js.xhtml Sun Jun 21 03:08:21 2020 +0300 @@ -0,0 +1,228 @@ + + + + + Hedgewars Flags + + + + + +

List of Hedgewars flags

+ + + diff -r 237d691e5069 -r 6c689729b745 misc/graves_js_anim.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/graves_js_anim.xhtml Sun Jun 21 03:08:21 2020 +0300 @@ -0,0 +1,319 @@ + + + + + Hedgewars Graves + + + + + +

List of Hedgewars graves

+ + +